From 8fb7eb6cd91a74151421c9f7dc105f6b60212dbb Mon Sep 17 00:00:00 2001 From: Michal Szymaniak Date: Sun, 21 Oct 2018 18:46:43 +0200 Subject: [PATCH] initial commit --- .gitignore | 24 + LICENSE | 674 ++ gizmo/.gitignore | 4 + gizmo/.ycm_extra_conf.py | 12 + gizmo/Makefile | 138 + gizmo/config/auto.mk | 12 + gizmo/config/linux.mk | 20 + gizmo/config/windows.mk | 25 + gizmo/correlator.cpp | 256 + gizmo/correlator.h | 95 + gizmo/extractor.cpp | 138 + gizmo/extractor.h | 52 + gizmo/general/exception.cpp | 195 + gizmo/general/exception.h | 66 + gizmo/general/logger.cpp | 129 + gizmo/general/logger.h | 22 + gizmo/general/sem.h | 40 + gizmo/general/thread.h | 34 + gizmo/math/line.cpp | 181 + gizmo/math/line.h | 39 + gizmo/math/linefinder.cpp | 112 + gizmo/math/linefinder.h | 38 + gizmo/math/point.cpp | 32 + gizmo/math/point.h | 24 + gizmo/media/audiodec.cpp | 144 + gizmo/media/audiodec.h | 43 + gizmo/media/audiodmx.cpp | 138 + gizmo/media/audiodmx.h | 37 + gizmo/media/audioout.h | 30 + gizmo/media/audioresampler.cpp | 131 + gizmo/media/audioresampler.h | 46 + gizmo/media/decoder.h | 29 + gizmo/media/demux.cpp | 249 + gizmo/media/demux.h | 66 + gizmo/media/speechrec.cpp | 204 + gizmo/media/speechrec.h | 46 + gizmo/media/stream.cpp | 228 + gizmo/media/stream.h | 48 + gizmo/media/subdec.cpp | 225 + gizmo/media/subdec.h | 66 + gizmo/python/correlator.cpp | 48 + gizmo/python/extractor.cpp | 21 + gizmo/python/general.cpp | 27 + gizmo/python/media.cpp | 101 + gizmo/python/stream.cpp | 50 + gizmo/python/translator.cpp | 28 + gizmo/python/wrapper.cpp | 24 + gizmo/test/catch.hpp | 13359 ++++++++++++++++++++++++++++ gizmo/test/core.cpp | 3 + gizmo/test/test.mk | 53 + gizmo/test/utf8.cpp | 203 + gizmo/text/dictionary.cpp | 65 + gizmo/text/dictionary.h | 31 + gizmo/text/translator.cpp | 49 + gizmo/text/translator.h | 25 + gizmo/text/utf8.cpp | 318 + gizmo/text/utf8.h | 60 + gizmo/text/utf8case.h | 217 + gizmo/text/words.h | 21 + gizmo/text/wordsqueue.cpp | 69 + gizmo/text/wordsqueue.h | 40 + requirements.txt | 12 + subsync/.gitignore | 6 + subsync/Makefile | 49 + subsync/__main__.py | 124 + subsync/assets/__init__.py | 45 + subsync/assets/assets.py | 111 + subsync/assets/downloader.py | 130 + subsync/assets/updater.py | 61 + subsync/config.py.template | 30 + subsync/data/__init__.py | 0 subsync/data/charenc.py | 46 + subsync/data/filetypes.py | 16 + subsync/data/languages.py | 46 + subsync/dictionary.py | 45 + subsync/encdetect.py | 31 + subsync/error.py | 75 + subsync/graph.py | 112 + subsync/gui/__init__.py | 0 subsync/gui/aboutwin.fbp | 958 ++ subsync/gui/aboutwin.py | 718 ++ subsync/gui/choiceenc.py | 24 + subsync/gui/choicelang.py | 31 + subsync/gui/downloadwin.fbp | 720 ++ subsync/gui/downloadwin.py | 84 + subsync/gui/errorwin.fbp | 649 ++ subsync/gui/errorwin.py | 68 + subsync/gui/filedlg.py | 46 + subsync/gui/filedrop.py | 28 + subsync/gui/mainwin.fbp | 1323 +++ subsync/gui/mainwin.py | 216 + subsync/gui/openwin.fbp | 1300 +++ subsync/gui/openwin.py | 97 + subsync/gui/settingswin.fbp | 3317 +++++++ subsync/gui/settingswin.py | 94 + subsync/gui/streamlist.py | 42 + subsync/gui/subpanel.fbp | 368 + subsync/gui/subpanel.py | 50 + subsync/gui/syncwin.fbp | 2564 ++++++ subsync/gui/syncwin.py | 287 + subsync/img.py | 55 + subsync/img/crossmark.png | Bin 0 -> 476 bytes subsync/img/icon.ico | Bin 0 -> 319616 bytes subsync/img/icon.png | Bin 0 -> 2824 bytes subsync/img/icon.svg | 4 + subsync/img/logo.png | Bin 0 -> 2824 bytes subsync/img/tickmark.png | Bin 0 -> 535 bytes subsync/img/warning.png | Bin 0 -> 1130 bytes subsync/key.pub | 14 + subsync/loggercfg.py | 55 + subsync/pipeline.py | 143 + subsync/pubkey.py | 24 + subsync/settings.py | 79 + subsync/setup.py | 71 + subsync/speech.py | 98 + subsync/stream.py | 83 + subsync/subtitle.py | 127 + subsync/synchro.py | 142 + subsync/thread.py | 124 + subsync/utils.py | 32 + tools/dict/banner.txt | 10 + tools/dict/dict-compile.py | 55 + tools/dict/dict_tools.py | 93 + tools/dict/language-codes-3b2.csv | 184 + tools/dict/sqlite2dict.py | 38 + tools/dict/wikdict-convert.py | 37 + tools/update_version.py | 36 + 127 files changed, 34331 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 gizmo/.gitignore create mode 100644 gizmo/.ycm_extra_conf.py create mode 100644 gizmo/Makefile create mode 100644 gizmo/config/auto.mk create mode 100644 gizmo/config/linux.mk create mode 100644 gizmo/config/windows.mk create mode 100644 gizmo/correlator.cpp create mode 100644 gizmo/correlator.h create mode 100644 gizmo/extractor.cpp create mode 100644 gizmo/extractor.h create mode 100644 gizmo/general/exception.cpp create mode 100644 gizmo/general/exception.h create mode 100644 gizmo/general/logger.cpp create mode 100644 gizmo/general/logger.h create mode 100644 gizmo/general/sem.h create mode 100644 gizmo/general/thread.h create mode 100644 gizmo/math/line.cpp create mode 100644 gizmo/math/line.h create mode 100644 gizmo/math/linefinder.cpp create mode 100644 gizmo/math/linefinder.h create mode 100644 gizmo/math/point.cpp create mode 100644 gizmo/math/point.h create mode 100644 gizmo/media/audiodec.cpp create mode 100644 gizmo/media/audiodec.h create mode 100644 gizmo/media/audiodmx.cpp create mode 100644 gizmo/media/audiodmx.h create mode 100644 gizmo/media/audioout.h create mode 100644 gizmo/media/audioresampler.cpp create mode 100644 gizmo/media/audioresampler.h create mode 100644 gizmo/media/decoder.h create mode 100644 gizmo/media/demux.cpp create mode 100644 gizmo/media/demux.h create mode 100644 gizmo/media/speechrec.cpp create mode 100644 gizmo/media/speechrec.h create mode 100644 gizmo/media/stream.cpp create mode 100644 gizmo/media/stream.h create mode 100644 gizmo/media/subdec.cpp create mode 100644 gizmo/media/subdec.h create mode 100644 gizmo/python/correlator.cpp create mode 100644 gizmo/python/extractor.cpp create mode 100644 gizmo/python/general.cpp create mode 100644 gizmo/python/media.cpp create mode 100644 gizmo/python/stream.cpp create mode 100644 gizmo/python/translator.cpp create mode 100644 gizmo/python/wrapper.cpp create mode 100644 gizmo/test/catch.hpp create mode 100644 gizmo/test/core.cpp create mode 100644 gizmo/test/test.mk create mode 100644 gizmo/test/utf8.cpp create mode 100644 gizmo/text/dictionary.cpp create mode 100644 gizmo/text/dictionary.h create mode 100644 gizmo/text/translator.cpp create mode 100644 gizmo/text/translator.h create mode 100644 gizmo/text/utf8.cpp create mode 100644 gizmo/text/utf8.h create mode 100644 gizmo/text/utf8case.h create mode 100644 gizmo/text/words.h create mode 100644 gizmo/text/wordsqueue.cpp create mode 100644 gizmo/text/wordsqueue.h create mode 100644 requirements.txt create mode 100644 subsync/.gitignore create mode 100644 subsync/Makefile create mode 100644 subsync/__main__.py create mode 100644 subsync/assets/__init__.py create mode 100644 subsync/assets/assets.py create mode 100644 subsync/assets/downloader.py create mode 100644 subsync/assets/updater.py create mode 100644 subsync/config.py.template create mode 100644 subsync/data/__init__.py create mode 100644 subsync/data/charenc.py create mode 100644 subsync/data/filetypes.py create mode 100644 subsync/data/languages.py create mode 100644 subsync/dictionary.py create mode 100644 subsync/encdetect.py create mode 100644 subsync/error.py create mode 100644 subsync/graph.py create mode 100644 subsync/gui/__init__.py create mode 100644 subsync/gui/aboutwin.fbp create mode 100644 subsync/gui/aboutwin.py create mode 100644 subsync/gui/choiceenc.py create mode 100644 subsync/gui/choicelang.py create mode 100644 subsync/gui/downloadwin.fbp create mode 100644 subsync/gui/downloadwin.py create mode 100644 subsync/gui/errorwin.fbp create mode 100644 subsync/gui/errorwin.py create mode 100644 subsync/gui/filedlg.py create mode 100644 subsync/gui/filedrop.py create mode 100644 subsync/gui/mainwin.fbp create mode 100644 subsync/gui/mainwin.py create mode 100644 subsync/gui/openwin.fbp create mode 100644 subsync/gui/openwin.py create mode 100644 subsync/gui/settingswin.fbp create mode 100644 subsync/gui/settingswin.py create mode 100644 subsync/gui/streamlist.py create mode 100644 subsync/gui/subpanel.fbp create mode 100644 subsync/gui/subpanel.py create mode 100644 subsync/gui/syncwin.fbp create mode 100644 subsync/gui/syncwin.py create mode 100644 subsync/img.py create mode 100644 subsync/img/crossmark.png create mode 100644 subsync/img/icon.ico create mode 100644 subsync/img/icon.png create mode 100644 subsync/img/icon.svg create mode 100644 subsync/img/logo.png create mode 100644 subsync/img/tickmark.png create mode 100644 subsync/img/warning.png create mode 100644 subsync/key.pub create mode 100644 subsync/loggercfg.py create mode 100644 subsync/pipeline.py create mode 100644 subsync/pubkey.py create mode 100644 subsync/settings.py create mode 100644 subsync/setup.py create mode 100644 subsync/speech.py create mode 100644 subsync/stream.py create mode 100644 subsync/subtitle.py create mode 100644 subsync/synchro.py create mode 100644 subsync/thread.py create mode 100644 subsync/utils.py create mode 100644 tools/dict/banner.txt create mode 100644 tools/dict/dict-compile.py create mode 100644 tools/dict/dict_tools.py create mode 100644 tools/dict/language-codes-3b2.csv create mode 100644 tools/dict/sqlite2dict.py create mode 100644 tools/dict/wikdict-convert.py create mode 100644 tools/update_version.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b158d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +*.o +*.d +*.a +*.so +*.dll +*.res +*.pyc +*.pyd +*.log +*.srt +*.words +*.dict +*.sqlite3 +*.pot +*.vim +*.dot +tags +.envrc +*.depend +*.layout +*.exe +*.msi +.env +CppCheckResults.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/gizmo/.gitignore b/gizmo/.gitignore new file mode 100644 index 0000000..71cfe51 --- /dev/null +++ b/gizmo/.gitignore @@ -0,0 +1,4 @@ +config.mk +ycm_flags.py +test/tests + diff --git a/gizmo/.ycm_extra_conf.py b/gizmo/.ycm_extra_conf.py new file mode 100644 index 0000000..d8647df --- /dev/null +++ b/gizmo/.ycm_extra_conf.py @@ -0,0 +1,12 @@ +import ycm_core + +def FlagsForFile(filename): + try: + from ycm_flags import flags + except: + flags = [ '-x', 'c++', '-Wall', '-Wextra', '-std=c++11', '-I.' ] + + return { + 'flags': flags, + 'do_cache': True + } diff --git a/gizmo/Makefile b/gizmo/Makefile new file mode 100644 index 0000000..e311ea2 --- /dev/null +++ b/gizmo/Makefile @@ -0,0 +1,138 @@ +############################################ +### load platform specific configuration ### +############################################ + +ifndef CFG + CFG = config.mk + ifeq (,$(wildcard $(CFG))) +$(info $(CFG) file not found, using auto configuration) +$(info ) + CFG = config/auto.mk + endif +endif + +include $(CFG) + + +##################################### +### default targets configuration ### +##################################### + +CXXFLAGS_REL ?= -O3 \ + -g \ + -fomit-frame-pointer \ + -fexpensive-optimizations \ + -DNDEBUG \ + +CXXFLAGS_DBG ?= -O0 \ + -g \ + +CXXFLAGS_PROF ?= -O3 \ + -pg \ + -fexpensive-optimizations \ + -DNDEBUG \ + + +#################################### +### default global configuration ### +#################################### +LIB_SUFFIX ?= .so +PREFIX ?= /usr/local +TARGET ?= ../subsync/gizmo$(LIB_SUFFIX) + + +CXXFLAGS += \ + -Wall \ + -Wextra \ + -pedantic \ + -fPIC \ + -std=c++14 \ + -fvisibility=hidden \ + -I. \ + +LDFLAGS += \ + -Wl,--as-needed \ + + +###################### +### artifact files ### +###################### + +SOURCES = \ + extractor.cpp \ + correlator.cpp \ + media/demux.cpp \ + media/stream.cpp \ + media/audiodec.cpp \ + media/audioresampler.cpp \ + media/audiodmx.cpp \ + media/subdec.cpp \ + media/speechrec.cpp \ + text/translator.cpp \ + text/dictionary.cpp \ + text/utf8.cpp \ + text/wordsqueue.cpp \ + math/point.cpp \ + math/line.cpp \ + math/linefinder.cpp \ + general/exception.cpp \ + general/logger.cpp \ + python/wrapper.cpp \ + python/general.cpp \ + python/extractor.cpp \ + python/media.cpp \ + python/stream.cpp \ + python/correlator.cpp \ + python/translator.cpp \ + +OBJECTS = $(SOURCES:.cpp=.o) + +DEPENDS = $(SOURCES:.cpp=.d) + + +##################### +### build recipes ### +##################### + +all: rel + +rel: CXXFLAGS += $(CXXFLAGS_REL) +rel: LDFLAGS += $(LDFLAGS_REL) +rel: $(TARGET) + $(info RELEASE target done) + +dbg: CXXFLAGS += $(CXXFLAGS_DBG) +dbg: LDFLAGS += $(LDFLAGS_DBG) +dbg: $(TARGET) + $(info DEBUG target done) + +prof: CXXFLAGS += $(CXXFLAGS_PROF) +prof: LDFLAGS += $(LDFLAGS_PROF) +prof: $(TARGET) + $(info PROFILE target done) + +clean: + $(RM) $(OBJECTS) + $(RM) $(DEPENDS) + $(RM) $(TARGET) + + +-include $(DEPENDS) + +$(OBJECTS): %.o: %.cpp + $(CXX) -c -o $@ $< $(CXXFLAGS) + $(CXX) -MM $(CXXFLAGS) $*.cpp -MF $*.d -MQ $@ + + +$(TARGET): $(OBJECTS) + $(CXX) -shared -o $@ $^ $(LDFLAGS) + + +ycm_flags.py: .FORCE + @echo "flags = '$(CXXFLAGS) $(LDFLAGS)'.split()" > $@ + +-include test/test.mk + +.PHONY: all build clean +.PHONY: rel dbg prof +.PHONY: .FORCE diff --git a/gizmo/config/auto.mk b/gizmo/config/auto.mk new file mode 100644 index 0000000..3bb86ff --- /dev/null +++ b/gizmo/config/auto.mk @@ -0,0 +1,12 @@ +ifeq ($(OS),Windows_NT) + include config/windows.mk +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Linux) + include config/linux.mk + else ifeq ($(UNAME_S),Darwin) + include config/macos.mk + else +$(error cannot detect architecture, you must select configuration manually) + endif +endif diff --git a/gizmo/config/linux.mk b/gizmo/config/linux.mk new file mode 100644 index 0000000..70b7b7f --- /dev/null +++ b/gizmo/config/linux.mk @@ -0,0 +1,20 @@ +PYTHON_PREFIX ?= python + +LIBS += \ + pocketsphinx \ + sphinxbase \ + libavdevice \ + libavformat \ + libavfilter \ + libavcodec \ + libswresample \ + libswscale \ + libavutil \ + +CXXFLAGS += $(shell pkg-config --cflags $(LIBS)) +LDFLAGS += $(shell pkg-config --libs $(LIBS)) + +CXXFLAGS += $(shell $(PYTHON_PREFIX) -m pybind11 --includes) + +LIB_SUFFIX ?= $(shell $(PYTHON_PREFIX)-config --extension-suffix) + diff --git a/gizmo/config/windows.mk b/gizmo/config/windows.mk new file mode 100644 index 0000000..e7ae473 --- /dev/null +++ b/gizmo/config/windows.mk @@ -0,0 +1,25 @@ +LIB_SUFFIX ?= .pyd + +CXXFLAGS += \ + -I$(POCKETSPHINX)\include \ + -I$(SPHINXBASE)\include \ + -I$(SPHINXBASE)\include\win32 \ + -I$(FFMPEG)\include \ + -D__STDC_CONSTANT_MACROS + +LDFLAGS += \ + -lsphinxbase \ + -lpocketsphinx \ + -L$(POCKETSPHINX)\bin\Release\x64 \ + -L$(SPHINXBASE)\bin\Release\x64 \ + -lavdevice \ + -lavformat \ + -lavfilter \ + -lavcodec \ + -lswresample \ + -lswscale \ + -lavutil \ + -L$(FFMPEG)\lib \ + $(PYTHONLIB) \ + +CXXFLAGS += $(shell python -m pybind11 --includes) diff --git a/gizmo/correlator.cpp b/gizmo/correlator.cpp new file mode 100644 index 0000000..0db7a02 --- /dev/null +++ b/gizmo/correlator.cpp @@ -0,0 +1,256 @@ +#include "correlator.h" +#include "math/point.h" +#include "math/line.h" +#include "text/utf8.h" +#include "text/translator.h" +#include "general/exception.h" +#include +#include + +using namespace std; + + +Correlator::Correlator( + float windowSize, + double minCorrelation, + float maxDistance, + unsigned minPointsNo, + float minWordsSim) : + m_running(false), + m_wordsNo(0), + m_lineFinder(5.0f, windowSize), + m_windowSize(windowSize), + m_minCorrelation(minCorrelation), + m_maxDistance(maxDistance), + m_minPointsNo(minPointsNo), + m_minWordsSim(minWordsSim) +{ +} + +Correlator::~Correlator() +{ + terminate(); +} + +void Correlator::pushSubWord(const string &word, double time) +{ + m_queue.push(WordId::Sub, word, time); +} + +void Correlator::pushRefWord(const string &word, double time) +{ + m_queue.push(WordId::Ref, word, time); +} + +void Correlator::run() +{ + string word; + double time; + + while (m_running) + { + WordId id = m_queue.pop(word, time); + m_wordsNo++; + + bool newBestLine = false; + + if (id == WordId::Sub) + newBestLine = addSubtitle(time, word); + else if (id == WordId::Ref) + newBestLine = addReference(time, word); + + if (newBestLine) + correlate(); + } + + m_threadEndSem.wait(); +} + +void Correlator::terminate() +{ + m_running = false; + + if (m_thread.joinable()) + { + m_queue.release(); + m_threadEndSem.post(); + m_thread.join(); + } + + m_threadEndSem.reset(); +} + +void Correlator::start() +{ + terminate(); + + m_running = true; + m_thread = thread(&Correlator::run, this); +} + +void Correlator::stop() +{ + m_running = false; +} + +bool Correlator::isRunning() const +{ + return m_running; +} + +bool Correlator::isDone() const +{ + return m_queue.empty(); +} + +float Correlator::getProgress() const +{ + size_t added = m_wordsNo; + size_t waiting = m_queue.size(); + size_t sum = added + waiting; + return sum > 0 ? (float) added / (float) sum : 0.0; +} + +void Correlator::connectStatsCallback(StatsCallback callback) +{ + m_statsCb = callback; +} + +bool Correlator::addSubtitle(double time, const string &word) +{ + m_subs.insert(make_pair(time, word)); + + auto first = m_refs.lower_bound(time - m_windowSize); + auto last = m_refs.upper_bound(time + m_windowSize); + if (first == m_refs.end()) + first = m_refs.begin(); + + bool newBestLine = false; + + for (auto it = first; it != last; ++it) + { + float sim = compareWords(word, it->second); + if (sim >= m_minWordsSim) + { + newBestLine |= m_lineFinder.addPoint(time, it->first); + } + } + + return newBestLine; +} + +bool Correlator::addReference(double time, const string &word) +{ + m_refs.insert(make_pair(time, word)); + + auto first = m_subs.lower_bound(time - m_windowSize); + auto last = m_subs.upper_bound(time + m_windowSize); + if (first == m_subs.end()) + first = m_subs.begin(); + + bool newBestLine = false; + + for (auto it = first; it != last; ++it) + { + float sim = compareWords(word, it->second); + if (sim >= m_minWordsSim) + { + newBestLine |= m_lineFinder.addPoint(it->first, time); + } + } + + return newBestLine; +} + +void Correlator::correlate() const +{ + double cor; + + Line bestLine = m_lineFinder.getBestLine(); + const Points &points = m_lineFinder.getPoints(); + Points hits = bestLine.getPointsInLine(points, 5.0f); + + Line line(hits, NULL, NULL, &cor); + float dist = line.findFurthestPoint(hits); + + while ((cor < m_minCorrelation || dist > m_maxDistance) + && (hits.size() > m_minPointsNo)) + { + dist = line.removeFurthestPoint(hits); + line = Line(hits, NULL, NULL, &cor); + } + + if (m_statsCb) + { + CorrelationStats stats; + + stats.correlated = + cor >= m_minCorrelation && + hits.size() >= m_minPointsNo && + dist <= m_maxDistance; + + stats.factor = cor; + stats.points = hits.size(); + stats.maxDistance = dist; + stats.formula = line; + + if (m_running) + m_statsCb(stats); + } +} + +static Correlator::ElementsVector entrysToVector(Correlator::Entrys entrys) +{ + Correlator::ElementsVector res; + res.reserve(entrys.size()); + + for (auto &element : entrys) + { + auto p = pair(element.first, element.second); + res.push_back(p); + } + return res; +} + +Correlator::ElementsVector Correlator::getSubs() const +{ + if (m_running) + throw EXCEPTION("subtitle words cannot be obtained when the correlator is running") + .module("Correlator"); + + return entrysToVector(m_subs); +} + +Correlator::ElementsVector Correlator::getRefs() const +{ + if (m_running) + throw EXCEPTION("reference words cannot be obtained when the correlator is running") + .module("Correlator"); + + return entrysToVector(m_refs); +} + + +/*** CorrelationStats ***/ + +CorrelationStats::CorrelationStats() : + correlated(false), + factor(0.0), + points(0), + maxDistance(0.0f) +{ +} + +string CorrelationStats::toString(const char *prefix, const char *suffix) const +{ + stringstream ss; + ss << prefix << fixed << setprecision(3) + << "correlated=" << std::boolalpha << correlated + << ", factor=" << 100.0 * factor << "%" + << ", points=" << points + << ", maxDist=" << maxDistance + << ", formula=" << formula.toString() + << suffix; + return ss.str(); +} + diff --git a/gizmo/correlator.h b/gizmo/correlator.h new file mode 100644 index 0000000..c48d7be --- /dev/null +++ b/gizmo/correlator.h @@ -0,0 +1,95 @@ +#ifndef __CORRELATOR_H__ +#define __CORRELATOR_H__ + +#include "math/linefinder.h" +#include "text/wordsqueue.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +struct CorrelationStats +{ + bool correlated; + double factor; + unsigned points; + float maxDistance; + Line formula; + + CorrelationStats(); + std::string toString( + const char *prefix=" StatsCallback; + typedef std::multimap Entrys; + typedef std::vector> ElementsVector; + + public: + Correlator( + float windowSize = HUGE_VALF, + double minCorrelation = 0.0, + float maxDistance = HUGE_VALF, + unsigned minPointsNo = 0, + float minWordsSim = 0.5); + + virtual ~Correlator(); + + void connectStatsCallback(StatsCallback callback); + + void start(); + void stop(); + + bool isRunning() const; + bool isDone() const; + float getProgress() const; + + void pushSubWord(const std::string &word, double time); + void pushRefWord(const std::string &word, double time); + + ElementsVector getSubs() const; + ElementsVector getRefs() const; + + private: + void run(); + void terminate(); + + bool addSubtitle(double time, const std::string &word); + bool addReference(double time, const std::string &word); + + void correlate() const; + + private: + Entrys m_subs; + Entrys m_refs; + + std::atomic_bool m_running; + std::thread m_thread; + Semaphore m_threadEndSem; + + WordsQueue m_queue; + std::atomic_size_t m_wordsNo; + + LineFinder m_lineFinder; + + StatsCallback m_statsCb; + CorrelationStats m_stats; + + double m_windowSize; + double m_minCorrelation; + float m_maxDistance; + unsigned m_minPointsNo; + float m_minWordsSim; +}; + +#endif diff --git a/gizmo/extractor.cpp b/gizmo/extractor.cpp new file mode 100644 index 0000000..7008171 --- /dev/null +++ b/gizmo/extractor.cpp @@ -0,0 +1,138 @@ +#include "extractor.h" +#include "general/thread.h" +#include "general/exception.h" +#include + +using namespace std; + + +Extractor::Extractor(Demux &demux) : + m_running(false), + m_demux(demux), + m_beginTime(0.0), + m_endTime(DBL_MAX) +{} + +Extractor::~Extractor() +{ + terminate(); +} + +const StreamsFormat &Extractor::getStreamsInfo() const +{ + return m_demux.getStreamsInfo(); +} + +void Extractor::selectTimeWindow(double begin, double end) +{ + if (begin > end || begin != begin || end != end) + throw EXCEPTION("invalid time window").module("extractor") + .add("begin", begin).add("end", end); + + m_beginTime = begin; + m_endTime = end; +} + +void Extractor::connectEosCallback(EosCallback callback) +{ + m_eosCb = callback; +} + +void Extractor::connectErrorCallback(ErrorCallback callback) +{ + m_errorCb = callback; +} + +void Extractor::start() +{ + terminate(); + m_running = true; + m_thread = thread(&Extractor::run, this); +} + +void Extractor::stop() +{ + m_running = false; +} + +bool Extractor::isRunning() const +{ + return m_running; +} + +void Extractor::run() +{ + lowerThreadPriority(); + + try + { + m_demux.seek(m_beginTime); + m_demux.start(); + } + catch (Exception &ex) + { + m_running = false; + + if (m_errorCb) + { + ex.add("terminated", 1); + m_errorCb(ex); + } + } + catch (const std::exception& ex) + { + m_errorCb(EXCEPTION(ex.what()).module("extractor").add("terminated", 1)); + } + catch (...) + { + m_errorCb(EXCEPTION("fatal error").module("extractor").add("terminated", 1)); + } + + while (m_running) + { + try + { + while (m_running) + { + if (!m_demux.step() || (m_demux.getPosition() >= m_endTime)) + { + m_running = false; + if (m_eosCb) + m_eosCb(); + } + } + } + catch (const Exception &ex) + { + m_demux.notifyDiscontinuity(); + + if (m_errorCb) + m_errorCb(ex); + } + catch (const std::exception& ex) + { + m_errorCb(EXCEPTION(ex.what()).module("extractor")); + } + catch (...) + { + m_errorCb(EXCEPTION("fatal error").module("extractor")); + } + } + + m_demux.stop(); + m_threadEndSem.wait(); +} + +void Extractor::terminate() +{ + m_running = false; + + if (m_thread.joinable()) + { + m_threadEndSem.post(); + m_thread.join(); + } + + m_threadEndSem.reset(); +} + diff --git a/gizmo/extractor.h b/gizmo/extractor.h new file mode 100644 index 0000000..76529f9 --- /dev/null +++ b/gizmo/extractor.h @@ -0,0 +1,52 @@ +#ifndef __EXTRACTOR_H__ +#define __EXTRACTOR_H__ + +#include "media/demux.h" +#include "general/sem.h" +#include "general/exception.h" +#include +#include +#include +#include + + +class Extractor +{ + public: + typedef std::function EosCallback; + typedef std::function ErrorCallback; + + public: + Extractor(Demux &demux); + ~Extractor(); + + const StreamsFormat &getStreamsInfo() const; + + void selectTimeWindow(double beginTime, double endTime); + + void connectEosCallback(EosCallback callback); + void connectErrorCallback(ErrorCallback callback); + + void start(); + void stop(); + bool isRunning() const; + + private: + void run(); + void terminate(); + + private: + std::thread m_thread; + Semaphore m_threadEndSem; + + std::atomic_bool m_running; + Demux &m_demux; + + double m_beginTime; + double m_endTime; + + EosCallback m_eosCb; + ErrorCallback m_errorCb; +}; + +#endif diff --git a/gizmo/general/exception.cpp b/gizmo/general/exception.cpp new file mode 100644 index 0000000..51603c9 --- /dev/null +++ b/gizmo/general/exception.cpp @@ -0,0 +1,195 @@ +#include "exception.h" +#include + +extern "C" +{ +#include +#include +} + + +using namespace std; + + +/*** Exception ***/ + +Exception::Exception() throw() +{} + +Exception::Exception(const string &msg) throw() : msg(msg) +{} + +Exception::Exception(const string &msg, const string &detail) throw() +{ + this->msg = msg + ": " + detail; +} + +Exception::~Exception() throw() +{} + +const char* Exception::what() const throw() +{ + if (str.empty()) + { + str = msg; + for (auto it = vals.begin(); it != vals.end(); ++it) + str += string("\n") + it->first + ": " + it->second; + } + + return str.c_str(); +} + +const char *Exception::message() const throw() +{ + return msg.c_str(); +} + +const map &Exception::fields() const throw() +{ + return vals; +} + +string &Exception::operator[] (const string &field) throw() +{ + str.clear(); + return vals[field]; +} + +const string &Exception::operator[] (const string &field) const throw() +{ + return get(field); +} + +Exception &Exception::add(const string &field, const string &val) throw() +{ + vals[field] = val; + str.clear(); + return *this; +} + +const string &Exception::get(const string &field) const throw() +{ + auto it = vals.find(field); + if (it == vals.end()) + { + static string empty; + return empty; + } + return it->second; +} + +Exception &Exception::module(const string &m) throw() +{ + string &mod = vals["module"]; + if (!mod.empty()) + mod += '.'; + + mod += m; + return *this; +} + +Exception &Exception::module(const string &m1, const string &m2, + const string &m3, const string &m4) throw() +{ + module(m1); + module(m2); + if (!m3.empty()) module(m3); + if (!m4.empty()) module(m4); + + return *this; +} + +Exception &Exception::code(int code) throw() +{ + return add("code", code); +} + +Exception &Exception::file(const string &file) throw() +{ + return add("file", file); +} + +Exception &Exception::line(const string &line) throw() +{ + return add("line", line); +} + +Exception &Exception::time(double timestamp) throw() +{ + double ip, fp; + fp = modf(timestamp, &ip) * 1000.0; + unsigned time = ip; + + char buffer[20]; + snprintf(buffer, sizeof(buffer)/sizeof(char), "%02u:%02u:%02u.%03u", + time / 3600, + time % 3600 / 60, + time % 60, + (unsigned)fp); + + return add("time", buffer); +} + + +/*** Helper functions ***/ + +string makeSourceString(const char *file, int line, const char *func) +{ + stringstream ss; + ss << file << ":" << line << " " << func; + return ss.str(); +} + +string ffmpegCodeDescription(int code) +{ + string res; + switch (code) + { + case AVERROR_BUG: + case AVERROR_BUG2: + res = "internal error"; + + case AVERROR_BUFFER_TOO_SMALL: + res = "buffer too small"; + + case AVERROR_DECODER_NOT_FOUND: + res = "decoder not found"; + + case AVERROR_DEMUXER_NOT_FOUND: + res = "demuxer not found"; + + case AVERROR_ENCODER_NOT_FOUND: + res = "encoder not found"; + + case AVERROR_EOF: + res = "eof"; + + case AVERROR_FILTER_NOT_FOUND: + res = "filter not found"; + + case AVERROR_INVALIDDATA: + res = "invalid data"; + + case AVERROR_MUXER_NOT_FOUND: + res = "muxer not found"; + + case AVERROR_OPTION_NOT_FOUND: + res = "option not found"; + + case AVERROR_PROTOCOL_NOT_FOUND: + res = "protocol not found"; + + case AVERROR_STREAM_NOT_FOUND: + res = "stream not found"; + } + + char buffer[1024]; + if (av_strerror(code, buffer, sizeof(buffer)) == 0) + { + if (!res.empty()) + res += ": "; + res += buffer; + } + + return res; +} diff --git a/gizmo/general/exception.h b/gizmo/general/exception.h new file mode 100644 index 0000000..584127c --- /dev/null +++ b/gizmo/general/exception.h @@ -0,0 +1,66 @@ +#ifndef __EXCEPTION_H__ +#define __EXCEPTION_H__ + +#include +#include +#include +#include + + +class Exception : public std::exception +{ + public: + Exception() throw(); + Exception(const std::string &msg) throw(); + Exception(const std::string &msg, const std::string &detail) throw(); + virtual ~Exception() throw(); + + virtual const char* what() const throw(); + const char *message() const throw(); + const std::map &fields() const throw(); + + std::string &operator[] (const std::string &field) throw(); + const std::string &operator[] (const std::string &field) const throw(); + + Exception &add(const std::string &field, const std::string &val) throw(); + + template + Exception &add(const std::string &field, const T &val) throw() + { + std::stringstream ss; + ss << val; + return add(field, ss.str()); + } + + Exception &module(const std::string &m) throw(); + Exception &module(const std::string &m1, const std::string &m2, + const std::string &m3="", const std::string &m4="") throw(); + Exception &code(int code) throw(); + Exception &file(const std::string &file) throw(); + Exception &line(const std::string &line) throw(); + Exception &time(double timestamp) throw(); + + const std::string &get(const std::string &field) const throw(); + + private: + std::string msg; + std::map vals; + + mutable std::string str; + mutable std::string fieldsStr; +}; + + +std::string makeSourceString(const char *file, int line, const char *func); +std::string ffmpegCodeDescription(int code); + +#define EXCEPTION_ADD_SOURCE \ + add("source", makeSourceString(__FILE__, __LINE__, __PRETTY_FUNCTION__)) + +#define EXCEPTION(msg) \ + Exception(msg).EXCEPTION_ADD_SOURCE + +#define EXCEPTION_FFMPEG(msg, val) \ + Exception(msg, ffmpegCodeDescription(val)).code(val).EXCEPTION_ADD_SOURCE + +#endif diff --git a/gizmo/general/logger.cpp b/gizmo/general/logger.cpp new file mode 100644 index 0000000..dbe81db --- /dev/null +++ b/gizmo/general/logger.cpp @@ -0,0 +1,129 @@ +#include "logger.h" +#include "text/utf8.h" +#include +#include + +extern "C" +{ +#include +} + + +static LoggerCallback g_loggerCallback = NULL; +static int g_ffmpegLogLevel = AV_LOG_WARNING; +static int g_sphinxLogLevel = ERR_WARN; + + +static void logCb(LogLevel level, const char *module, const char *msg) +{ + if (Utf8::validate(msg)) + { + if (g_loggerCallback) + g_loggerCallback(level, module, msg); + } + else + { + std::string m = Utf8::escape(msg) + Utf8::encode(0xffff); + if (g_loggerCallback) + g_loggerCallback(level, module, m.c_str()); + } +} + +static void ffmpegLogCb(void *avcl, int level, const char *fmt, va_list vl) +{ + if (g_loggerCallback && level >= g_ffmpegLogLevel) + { + static const int lineSize = 1024; + char line[lineSize]; + static int printPrefix = 1; + av_log_format_line(avcl, level, fmt, vl, line, lineSize, &printPrefix); + + LogLevel lvl = LOG_DEBUG; + if (level >= AV_LOG_DEBUG) lvl = LOG_DEBUG; + else if (level >= AV_LOG_INFO) lvl = LOG_INFO; + else if (level >= AV_LOG_WARNING) lvl = LOG_WARNING; + else if (level >= AV_LOG_ERROR) lvl = LOG_ERROR; + else if (level >= AV_LOG_FATAL) lvl = LOG_CRITICAL; + + logCb(lvl, "ffmpeg", line); + } +} + +static void sphinxLogCb(void *user_data, err_lvl_t level, const char *fmt, ...) +{ + (void) user_data; + + if (g_loggerCallback && level != ERR_INFOCONT && level >= g_sphinxLogLevel) + { + static const int lineSize = 1024; + char line[lineSize]; + + va_list args; + va_start(args, fmt); + vsnprintf(line, lineSize, fmt, args); + va_end(args); + + LogLevel lvl = LOG_INFO; + switch (level) + { + case ERR_DEBUG: lvl = LOG_DEBUG; break; + case ERR_INFO: lvl = LOG_INFO; break; + case ERR_INFOCONT: lvl = LOG_INFO; break; + case ERR_WARN: lvl = LOG_WARNING; break; + case ERR_ERROR: lvl = LOG_ERROR; break; + case ERR_FATAL: lvl = LOG_CRITICAL; break; + default: lvl = LOG_DEBUG; + } + + logCb(lvl, "sphinx", line); + } +} + +void setDebugLevel(int level) +{ + int ffmpeg = AV_LOG_WARNING; + int sphinx = ERR_WARN; + + if (level >= LOG_CRITICAL) + { + ffmpeg = AV_LOG_FATAL; + sphinx = ERR_FATAL; + } + else if (level >= LOG_ERROR) + { + ffmpeg = AV_LOG_ERROR; + sphinx = ERR_ERROR; + } + else if (level >= LOG_WARNING) + { + ffmpeg = AV_LOG_WARNING; + sphinx = ERR_WARN; + } + else if (level >= LOG_INFO) + { + ffmpeg = AV_LOG_INFO; + + // don't show INFO logs from sphinx here since there are tons of it + // and they are more like DEBUG logs anyway + sphinx = ERR_WARN; + } + else + { + ffmpeg = AV_LOG_DEBUG; + sphinx = ERR_DEBUG; + } + + g_ffmpegLogLevel = ffmpeg; + g_sphinxLogLevel = sphinx; + + av_log_set_level(ffmpeg); +} + +void setLoggerCallback(LoggerCallback cb) +{ + g_loggerCallback = cb; + av_log_set_callback(ffmpegLogCb); + + err_set_callback(sphinxLogCb, NULL); + err_set_logfp(NULL); +} diff --git a/gizmo/general/logger.h b/gizmo/general/logger.h new file mode 100644 index 0000000..c0fd2b1 --- /dev/null +++ b/gizmo/general/logger.h @@ -0,0 +1,22 @@ +#ifndef __LOGGER_H__ +#define __LOGGER_H__ + +#include + + +enum LogLevel +{ + LOG_NOTSET = 0, + LOG_DEBUG = 10, + LOG_INFO = 20, + LOG_WARNING = 30, + LOG_ERROR = 40, + LOG_CRITICAL = 50 +}; + +typedef std::function LoggerCallback; + +void setDebugLevel(int level); +void setLoggerCallback(LoggerCallback cb); + +#endif diff --git a/gizmo/general/sem.h b/gizmo/general/sem.h new file mode 100644 index 0000000..763c2b0 --- /dev/null +++ b/gizmo/general/sem.h @@ -0,0 +1,40 @@ +#ifndef __SEM_H__ +#define __SEM_H__ + +#include +#include + + +class Semaphore +{ + public: + Semaphore (int count = 0) : m_count(count) + {} + + inline void post() + { + std::unique_lock lock(m_mutex); + m_count++; + m_cond.notify_one(); + } + + inline void wait() + { + std::unique_lock lock(m_mutex); + while(m_count == 0) m_cond.wait(lock); + m_count--; + } + + inline void reset() + { + std::unique_lock lock(m_mutex); + m_count = 0; + } + + private: + std::mutex m_mutex; + std::condition_variable m_cond; + int m_count; +}; + +#endif diff --git a/gizmo/general/thread.h b/gizmo/general/thread.h new file mode 100644 index 0000000..740bc61 --- /dev/null +++ b/gizmo/general/thread.h @@ -0,0 +1,34 @@ +#ifndef __THREAD_H__ +#define __THREAD_H__ + +#if defined(__linux__) + +#include + +void lowerThreadPriority() +{ + struct sched_param param; + param.sched_priority = 0; + pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); +} + +#elif defined(_WIN32) + +#include + +void lowerThreadPriority() +{ + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); +} + +#else + +#pragma message("warning: Unknown platform, thread priority control not supported") + +void lowerThreadPriority() +{ +} + +#endif + +#endif diff --git a/gizmo/math/line.cpp b/gizmo/math/line.cpp new file mode 100644 index 0000000..c9242a2 --- /dev/null +++ b/gizmo/math/line.cpp @@ -0,0 +1,181 @@ +#include "line.h" +#include +#include +#include + +using namespace std; + + +Line::Line() : a(1.0f), b(0.0f) +{} + +Line::Line(float a, float b) : a(a), b(b) +{} + +Line::Line(const Point &p1, const Point &p2) +{ + a = (p1.y - p2.y) / (p1.x - p2.x); + b = p1.y - a*p1.x; +} + +Line::Line(const Points &points, double *da, double *db, double *cor) +{ + if (points.size() < 2) + { + a = 1.0f; + b = 0.0f; + if (da && db) + { + *da = HUGE_VALF; + *db = HUGE_VALF; + } + if (cor) + *cor = 0.0f; + } + + double sx = 0.0f; // sum of x + double sy = 0.0f; // sum of y + double sxy = 0.0f; // sum of x*y + double sx2 = 0.0f; // sum of x^2 + double sy2 = 0.0f; // sum of y^2 + + for (auto &point : points) + { + double x = point.x; + double y = point.y; + sx += x; + sy += y; + sxy += x*y; + sx2 += x*x; + sy2 += y*y; + } + + double n = points.size(); + + double txy = n*sxy - sx*sy; + double tx = n*sx2 - sx*sx; + double ty = n*sy2 - sy*sy; + + if ((tx == 0.0) || (ty == 0.0)) + { + if (cor) + *cor = 0.0; + return; + } + + a = txy / tx; + b = (sy - a*sx) / n; + + if (da && db) + { + double t3 = sy2 - a*sxy - b*sy; + + *da = sqrt(n/(n-2) * t3/tx); + *db = *da * sqrt(sx2/n); + } + + if (cor) + *cor = (txy * txy) / (tx * ty); +} + +float Line::getDistance(const Point &point) const +{ + return sqrt(getDistanceSqr(point)); +} + +float Line::getDistanceSqr(const Point &point) const +{ + float t = b + a*point.x - point.y; + return (t*t) / (a*a + 1.0f); +} + +Points Line::getPointsInLine(const Points &pts) const +{ + Points res; + for (auto point : pts) + { + if (point.y == a*point.x + b) + res.insert(point); + } + return res; +} + +Points Line::getPointsInLine(const Points &points, float maxDist) const +{ + Points res; + float maxDistSqr = maxDist * maxDist; + + for (auto point : points) + { + if (getDistanceSqr(point) <= maxDistSqr) + res.insert(point); + } + return res; +} + +size_t Line::countPointsInLine(const Points &points) const +{ + size_t cnt = 0; + for (auto point : points) + { + if (point.y == a*point.x + b) + cnt++; + } + return cnt; +} + +size_t Line::countPointsInLine(const Points &points, float maxDist) const +{ + size_t cnt = 0; + float maxDistSqr = maxDist * maxDist; + + for (auto point : points) + { + if (getDistanceSqr(point) <= maxDistSqr) + cnt++; + } + return cnt; +} + +float Line::findFurthestPoint(Points &points) const +{ + float furthestDist = 0.0f; + + for (auto point=points.begin(); point!=points.end(); ++point) + { + float dist = getDistanceSqr(*point); + if (dist > furthestDist) + furthestDist = dist; + } + + return sqrt(furthestDist); +} + +float Line::removeFurthestPoint(Points &points) const +{ + Points::iterator furthestPoint; + float furthestDist = 0.0f; + + for (auto point=points.begin(); point!=points.end(); ++point) + { + float dist = getDistanceSqr(*point); + if (dist > furthestDist) + { + furthestPoint = point; + furthestDist = dist; + } + } + + if (furthestDist > 0.0f) + points.erase(furthestPoint); + + return sqrt(furthestDist); +} + +string Line::toString() const +{ + stringstream ss; + ss << fixed << setprecision(3) << a << "x" << showpos << b; + return ss.str(); +} + diff --git a/gizmo/math/line.h b/gizmo/math/line.h new file mode 100644 index 0000000..0b2aac1 --- /dev/null +++ b/gizmo/math/line.h @@ -0,0 +1,39 @@ +#ifndef __LINE_H__ +#define __LINE_H__ + +#include "point.h" +#include +#include + + +class Line +{ + public: + float a, b; + + public: + Line(); + Line(float a, float b); + Line(const Point &p1, const Point &p2); + Line(const Points &points, double *da=NULL, double *db=NULL, + double *cor=NULL); + + inline float getX(float y) const { return (y-b) / a; } + inline float getY(float x) const { return a*x + b; } + + float getDistanceSqr(const Point &point) const; + float getDistance(const Point &point) const; + + Points getPointsInLine(const Points &points) const; + Points getPointsInLine(const Points &points, float maxDist) const; + + size_t countPointsInLine(const Points &points) const; + size_t countPointsInLine(const Points &points, float maxDist) const; + + float findFurthestPoint(Points &points) const; + float removeFurthestPoint(Points &points) const; + + std::string toString() const; +}; + +#endif diff --git a/gizmo/math/linefinder.cpp b/gizmo/math/linefinder.cpp new file mode 100644 index 0000000..e8df2dc --- /dev/null +++ b/gizmo/math/linefinder.cpp @@ -0,0 +1,112 @@ +#include "linefinder.h" + +using namespace std; + + +const static float QUADRANT_LEN = 60.f; +const static float MAX_LINE_SLOPE = 10.f; + + +LineFinder::LineFinder(float maxError, float maxDistance) : + m_bestPointsNo(0), + m_maxError(maxError), + m_maxDistance(maxDistance), + m_minX(HUGE_VALF), + m_maxX(-HUGE_VALF) +{ +} + +bool LineFinder::addPoint(const Point &point) +{ + if (m_points.count(point)) + return false; + + bool newBestLine = false; + const float maxErrorSquared = m_maxError * m_maxError; + + for (const Point &p1 : m_points) + { + Line line(p1, point); + + // line must be monotonically increasing + if (line.a < 1.f / MAX_LINE_SLOPE || line.a > MAX_LINE_SLOPE) + continue; + + if (point.x < m_minX) + m_minX = point.x; + if (point.x > m_maxX) + m_maxX = point.x; + + if (std::abs(line.getY(m_minX) - m_minX) > m_maxDistance) + continue; + if (std::abs(line.getY(m_maxX) - m_maxX) > m_maxDistance) + continue; + + size_t pointsNo = 1; + + for (const auto &col : m_quadrants) + { + const int qx = col.first; + const float y1 = line.getY((float) qx * QUADRANT_LEN); + const float y2 = line.getY((float) (qx + 1) * QUADRANT_LEN); + const int qy1 = floor((y1 - m_maxError) / QUADRANT_LEN); + const int qy2 = floor((y2 + m_maxError) / QUADRANT_LEN); + + const auto &row = col.second; + auto it = row.upper_bound(qy1); + + while (it != row.end() && it->first <= qy2) + { + for (const auto &p2 : it->second) + { + if (line.getDistanceSqr(p2) <= maxErrorSquared) + pointsNo++; + } + + ++it; + } + } + + if (pointsNo > m_bestPointsNo) + { + m_bestLine = line; + m_bestPointsNo = pointsNo; + newBestLine = true; + + // since only one point was added, pointsNo could be only one + // point larger than m_bestPointsNo, so there is no need to check + // other possible lines + break; + } + } + + m_points.insert(point); + + const int qx = floor(point.x / QUADRANT_LEN); + const int qy = floor(point.y / QUADRANT_LEN); + m_quadrants[qx][qy].push_back(point); + + return newBestLine; +} + +bool LineFinder::addPoint(float x, float y) +{ + return addPoint(Point(x, y)); +} + +const Line &LineFinder::getBestLine(size_t *pointsNo) const +{ + if (pointsNo) + *pointsNo = m_bestPointsNo; + return m_bestLine; +} + +const Points &LineFinder::getPoints() const +{ + return m_points; +} + +size_t LineFinder::getAlignedPointsNo() const +{ + return m_bestPointsNo; +} diff --git a/gizmo/math/linefinder.h b/gizmo/math/linefinder.h new file mode 100644 index 0000000..e85aa1d --- /dev/null +++ b/gizmo/math/linefinder.h @@ -0,0 +1,38 @@ +#ifndef __LINEFINDER_H__ +#define __LINEFINDER_H__ + +#include "line.h" +#include "point.h" +#include +#include +#include +#include +#include + + +class LineFinder +{ + public: + LineFinder(float maxError=5.0f, float maxDistance=HUGE_VALF); + + bool addPoint(const Point &point); + bool addPoint(float x, float y); + const Points &getPoints() const; + + const Line &getBestLine(size_t *pointsNo=NULL) const; + size_t getAlignedPointsNo() const; + + private: + Points m_points; + Line m_bestLine; + size_t m_bestPointsNo; + + std::unordered_map>> + m_quadrants; + + const float m_maxError; + const float m_maxDistance; + float m_minX, m_maxX; +}; + +#endif diff --git a/gizmo/math/point.cpp b/gizmo/math/point.cpp new file mode 100644 index 0000000..3601865 --- /dev/null +++ b/gizmo/math/point.cpp @@ -0,0 +1,32 @@ +#include "point.h" +#include + + +Point::Point() : x(0.0f), y(0.0f) +{} + +Point::Point(const Point &p) : x(p.x), y(p.y) +{} + +Point::Point(float x, float y) : x(x), y(y) +{} + +bool Point::operator== (const Point &p) const +{ + return x == p.x && y == p.y; +} + +bool Point::operator!= (const Point &p) const +{ + return x != p.x || y != p.y; +} + +bool Point::operator< (const Point &p) const +{ + return x == p.x ? y < p.y : x < p.x; +} + +float Point::getDistance(const Point &p) const +{ + return sqrt((p.x-x)*(p.x-x) + (p.y-y)*(p.y-y)); +} diff --git a/gizmo/math/point.h b/gizmo/math/point.h new file mode 100644 index 0000000..a2ae3c7 --- /dev/null +++ b/gizmo/math/point.h @@ -0,0 +1,24 @@ +#ifndef __POINT_H__ +#define __POINT_H__ + +#include + + +struct Point +{ + float x, y; + + Point(); + Point(const Point &p); + Point(float x, float y); + + bool operator== (const Point &p) const; + bool operator!= (const Point &p) const; + bool operator< (const Point &p) const; + + float getDistance(const Point &p) const; +}; + +typedef std::set Points; + +#endif diff --git a/gizmo/media/audiodec.cpp b/gizmo/media/audiodec.cpp new file mode 100644 index 0000000..5365b67 --- /dev/null +++ b/gizmo/media/audiodec.cpp @@ -0,0 +1,144 @@ +#include "audiodec.h" +#include "demux.h" +#include "audioout.h" +#include "general/exception.h" + +using namespace std; + + +AudioDec::AudioDec(const Demux *demux, unsigned streamId) + : AudioDec(demux->getStreamRawData(streamId)) +{ +} + +AudioDec::AudioDec(AVStream *stream) : + m_codec(NULL), + m_codecCtx(NULL), + m_frame(NULL), + m_output(NULL), + m_sampleSizeChs(0), + m_timeBase(0.0), + m_position(0.0) +{ + m_codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (m_codec == NULL) + throw EXCEPTION("can't find suitable audio codec") + .module("AudioDec", "avcodec_find_decoder"); + + m_codecCtx = avcodec_alloc_context3(m_codec); + + if (avcodec_parameters_to_context(m_codecCtx, stream->codecpar) < 0) + throw EXCEPTION("can't set audio codec context") + .module("AudioDec", "avcodec_parameters_to_context"); + + if (m_codecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + throw EXCEPTION("this is not audio stream") + .module("AudioDec"); + + AVCodecParameters *cp = stream->codecpar; + int bytesPerSample = av_get_bytes_per_sample((AVSampleFormat) cp->format); + m_sampleSizeChs = bytesPerSample * cp->channels; + + m_timeBase = (double)stream->time_base.num/(double)stream->time_base.den; +} + +AudioDec::~AudioDec() +{ + avcodec_free_context(&m_codecCtx); +} + +void AudioDec::start() +{ + int res = avcodec_open2(m_codecCtx, m_codec, NULL); + if (res < 0) + throw EXCEPTION_FFMPEG("can't open audio stream", res) + .module("AudioDec", "avcodec_open2"); + + m_frame = av_frame_alloc(); + + if (m_output) + m_output->start(); +} + +void AudioDec::stop() +{ + if (m_output) + m_output->stop(); + + av_frame_free(&m_frame); + avcodec_close(m_codecCtx); +} + +void AudioDec::connectOutput(AudioOutput *output) +{ + m_output = output; +} + +AudioFormat AudioDec::getFormat() const +{ + return AudioFormat(m_codecCtx); +} + +bool AudioDec::feed(AVPacket &packet) +{ + int ret = avcodec_send_packet(m_codecCtx, &packet); + if ((ret < 0) && (ret != AVERROR(EAGAIN)) && (ret != AVERROR_EOF)) + throw EXCEPTION_FFMPEG("audio decoder failed", ret) + .module("AudioDec", "avcodec_send_packet") + .time((double)packet.pts * m_timeBase); + + bool gotFrame = false; + while (true) + { + ret = avcodec_receive_frame(m_codecCtx, m_frame); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return gotFrame; + + if (ret < 0) + throw EXCEPTION_FFMPEG("audio decoder failed", ret) + .module("AudioDec", "avcodec_receive_frame") + .time((double)packet.pts * m_timeBase); + + m_position = (double)packet.pts * m_timeBase; + + if (m_output) + { + m_output->onNewData( + (const uint8_t*) m_frame->extended_data, + m_frame->nb_samples * m_sampleSizeChs, + m_position); + } + + gotFrame = true; + } +} + +void AudioDec::flush() +{ + AVPacket packet; + av_init_packet(&packet); + packet.data = NULL; + packet.size = 0; + while (feed(packet)); +} + +void AudioDec::discontinuity() +{ + if (m_output) + m_output->onDiscontinuity(); +} + +double AudioDec::getPosition() const +{ + return m_position; +} + +ConnectedAudioOutputs AudioDec::getConnectedOutputs() const +{ + ConnectedAudioOutputs outs; + if (m_output) + outs.push_back(ConnectedAudioOutput("", m_output)); + return outs; +} + diff --git a/gizmo/media/audiodec.h b/gizmo/media/audiodec.h new file mode 100644 index 0000000..5f27ba4 --- /dev/null +++ b/gizmo/media/audiodec.h @@ -0,0 +1,43 @@ +#ifndef __AUDIO_DECODER_H__ +#define __AUDIO_DECODER_H__ + +#include "decoder.h" +#include "stream.h" +#include "audioout.h" + +class Demux; +class AudioOutput; + + +class AudioDec : public Decoder +{ + public: + AudioDec(const Demux *demux, unsigned streamId); + AudioDec(AVStream *stream); + virtual ~AudioDec(); + + virtual void start(); + virtual void stop(); + + virtual AudioFormat getFormat() const; + + void connectOutput(AudioOutput *output); + + virtual bool feed(AVPacket &packet); + virtual void flush(); + virtual void discontinuity(); + + virtual double getPosition() const; + + ConnectedAudioOutputs getConnectedOutputs() const; + + private: + AVCodec *m_codec; + AVCodecContext *m_codecCtx; + AVFrame *m_frame; + AudioOutput *m_output; + int m_sampleSizeChs; // sampleSize * channelsNo + double m_timeBase; + double m_position; +}; +#endif diff --git a/gizmo/media/audiodmx.cpp b/gizmo/media/audiodmx.cpp new file mode 100644 index 0000000..6289788 --- /dev/null +++ b/gizmo/media/audiodmx.cpp @@ -0,0 +1,138 @@ +#include "audiodmx.h" +#include "general/exception.h" +#include +#include + +using namespace std; + + +static const size_t BUFFER_SIZE = 2048; + + +AudioDemux::AudioDemux() : + m_sampleSize(0), + m_channelsNo(0), + m_buffer(NULL), + m_channelSize(0), + m_pos(0), + m_timestamp(0.0) +{ +} + +AudioDemux::~AudioDemux() +{ + delete [] m_buffer; +} + +void AudioDemux::setOutputFormat(unsigned sampleSize, unsigned channelsNo) +{ + m_sampleSize = sampleSize; + m_channelsNo = channelsNo; + m_outputs.resize(m_channelsNo); + m_channelSize = BUFFER_SIZE * m_sampleSize; + + delete [] m_buffer; + m_buffer = new uint8_t[m_channelSize * m_channelsNo]; +} + +void AudioDemux::setOutputFormat(const AudioFormat &format) +{ + setOutputFormat(format.getSampleSize(), format.channelsNo); +} + +void AudioDemux::connectOutputChannel(unsigned channelNo, AudioOutput *output) +{ + if (m_channelsNo == 0) + throw EXCEPTION("output format not set").module("AudioDemux"); + + if (channelNo >= m_channelsNo) + throw EXCEPTION("channel number out of range") + .module("AudioDemux") + .add("channel", channelNo) + .add("max_channel", m_channelsNo - 1); + + m_outputs[channelNo] = output; +} + +void AudioDemux::start() +{ + for (AudioOutput *out : m_outputs) + { + if (out) + out->start(); + } +} + +void AudioDemux::stop() +{ + for (AudioOutput *out : m_outputs) + { + if (out) + out->stop(); + } +} + +void AudioDemux::onNewData(const uint8_t *data, size_t size, double ts) +{ + unsigned ch = 0; + + if (m_pos == 0) + m_timestamp = ts; + + try + { + for (size_t i = 0; i < size; i += m_sampleSize) + { + const uint8_t *src = data + i; + uint8_t *dst = m_buffer + ch*m_channelSize + m_pos; + memcpy(dst, src, m_sampleSize); + + if (++ch >= m_channelsNo) + { + m_pos += m_sampleSize; + if (m_pos >= m_channelSize) + { + for (unsigned ch = 0; ch < m_channelsNo; ch++) + { + AudioOutput *out = m_outputs[ch]; + if (out) + out->onNewData(m_buffer+ch*m_channelSize, + m_channelSize, m_timestamp); + } + m_pos = 0; + m_timestamp = ts; + } + ch = 0; + } + } + } + catch (...) + { + m_pos = 0; + throw; + } +} + +void AudioDemux::onDiscontinuity() +{ + m_pos = 0; + + for (unsigned ch = 0; ch < m_channelsNo; ch++) + { + AudioOutput *output = m_outputs[ch]; + if (output) + output->onDiscontinuity(); + } +} + +ConnectedAudioOutputs AudioDemux::getConnectedOutputs() const +{ + ConnectedAudioOutputs outs; + for (unsigned ch = 0; ch < m_channelsNo; ch++) + { + AudioOutput *output = m_outputs[ch]; + if (output) + outs.push_back(ConnectedAudioOutput("channel" + to_string(ch), output)); + } + return outs; +} diff --git a/gizmo/media/audiodmx.h b/gizmo/media/audiodmx.h new file mode 100644 index 0000000..2de75ed --- /dev/null +++ b/gizmo/media/audiodmx.h @@ -0,0 +1,37 @@ +#ifndef __AUDIO_DEMUX_H__ +#define __AUDIO_DEMUX_H__ + +#include "stream.h" +#include "audioout.h" +#include + + +class AudioDemux : public AudioOutput +{ + public: + AudioDemux(); + virtual ~AudioDemux(); + + void setOutputFormat(const AudioFormat &format); + void setOutputFormat(unsigned sampleSize, unsigned channelsNo); + void connectOutputChannel(unsigned channelNo, AudioOutput *output); + + virtual void start(); + virtual void stop(); + + virtual void onNewData(const uint8_t *data, size_t size, double timestamp); + virtual void onDiscontinuity(); + + ConnectedAudioOutputs getConnectedOutputs() const; + + private: + unsigned m_sampleSize; + unsigned m_channelsNo; + + std::vector m_outputs; + uint8_t *m_buffer; + size_t m_channelSize; // size of buffer for single channel in bytes + size_t m_pos; // position in buffer + double m_timestamp; // timestamp of first packet in the buffer +}; +#endif diff --git a/gizmo/media/audioout.h b/gizmo/media/audioout.h new file mode 100644 index 0000000..0d01a92 --- /dev/null +++ b/gizmo/media/audioout.h @@ -0,0 +1,30 @@ +#ifndef __AUDIO_OUTPUT_H__ +#define __AUDIO_OUTPUT_H__ + +#include +#include +#include +#include + + +class AudioOutput +{ + public: + virtual ~AudioOutput() {}; + + virtual void start() = 0; + virtual void stop() = 0; + + virtual void onNewData( + const uint8_t *data, + size_t size, + double timestamp) = 0; + + virtual void onDiscontinuity() = 0; +}; + + +typedef std::tuple ConnectedAudioOutput; +typedef std::vector ConnectedAudioOutputs; + +#endif diff --git a/gizmo/media/audioresampler.cpp b/gizmo/media/audioresampler.cpp new file mode 100644 index 0000000..298144c --- /dev/null +++ b/gizmo/media/audioresampler.cpp @@ -0,0 +1,131 @@ +#include "audioresampler.h" +#include "audioout.h" +#include "general/exception.h" + +using namespace std; + + +AudioResampler::AudioResampler() : + m_output(NULL), + m_swr(NULL), + m_buffer(NULL), + m_bufferSize(0), + m_inSampleSizeChs(0), + m_outSampleSizeChs(0) +{ +} + +AudioResampler::~AudioResampler() +{ + if (m_swr) + swr_free(&m_swr); + + if (m_buffer) + av_freep(&m_buffer); + + m_bufferSize = 0; +} + +void AudioResampler::connectOutput(AudioOutput *output) +{ + m_output = output; +} + +void AudioResampler::setParams(const AudioFormat &in, const AudioFormat &out, + const vector &mixMap, int bufferSize) +{ + int res; + + m_swr = swr_alloc_set_opts(m_swr, + out.channelLayout, out.sampleFormat, out.sampleRate, + in.channelLayout, in.sampleFormat, in.sampleRate, + 0, NULL); + + if (m_swr == NULL) + throw EXCEPTION("can't initialize resampler context") + .module("AudioResampler", "swr_alloc_set_opts") + .add("input", in.toString()) + .add("output", out.toString()); + + m_inSampleSizeChs = in.getSampleSize() * in.channelsNo; + m_outSampleSizeChs = out.getSampleSize() * out.channelsNo; + + if (!mixMap.empty()) + { + res = swr_set_matrix(m_swr, mixMap.data(), out.channelsNo); + if (res < 0) + throw EXCEPTION_FFMPEG("can't initialize audio mixer", res) + .module("AudioResampler", "swr_set_matrix"); + } + + res = swr_init(m_swr); + if (res < 0) + throw EXCEPTION_FFMPEG("can't initialize audio resampler", res) + .module("AudioResampler", "swr_init") + .add("input", in.toString()) + .add("output", out.toString()); + + if (m_bufferSize != bufferSize) + { + if (m_buffer) + av_freep(&m_buffer); + + m_bufferSize = bufferSize; + res = av_samples_alloc(&m_buffer, + NULL, + in.channelsNo, + m_bufferSize, + out.sampleFormat, + 0); + + if (res < 0) + throw EXCEPTION_FFMPEG("can't allocate sample buffer", res) + .module("AudioResampler", "av_samples_alloc") + .add("channels", in.channelsNo) + .add("size", m_bufferSize); + } +} + +void AudioResampler::start() +{ + if (m_output) + m_output->start(); +} + +void AudioResampler::stop() +{ + if (m_output) + m_output->stop(); +} + +void AudioResampler::onNewData(const uint8_t *data, size_t size, double timestamp) +{ + int res = swr_convert( + m_swr, + &m_buffer, + m_bufferSize, + (const uint8_t**) data, + size / m_inSampleSizeChs); + + if (res < 0) + throw EXCEPTION_FFMPEG("error during audio conversion", res) + .module("AudioResampler", "swr_convert") + .time(timestamp); + + if (m_output) + m_output->onNewData(m_buffer, res*m_outSampleSizeChs, timestamp); +} + +void AudioResampler::onDiscontinuity() +{ + if (m_output) + m_output->onDiscontinuity(); +} + +ConnectedAudioOutputs AudioResampler::getConnectedOutputs() const +{ + ConnectedAudioOutputs outs; + if (m_output) + outs.push_back(ConnectedAudioOutput("", m_output)); + return outs; +} diff --git a/gizmo/media/audioresampler.h b/gizmo/media/audioresampler.h new file mode 100644 index 0000000..45be3a6 --- /dev/null +++ b/gizmo/media/audioresampler.h @@ -0,0 +1,46 @@ +#ifndef __AUDIO_RESAMPLER__ +#define __AUDIO_RESAMPLER__ + +#include "audioout.h" +#include "stream.h" +#include +#include + +extern "C" +{ +#include +} + + +class AudioResampler : public AudioOutput +{ + public: + AudioResampler(); + virtual ~AudioResampler(); + + void connectOutput(AudioOutput *output); + + void setParams( + const AudioFormat &in, + const AudioFormat &out, + const std::vector &mixMap, + int bufferSize = 32*1024); + + virtual void start(); + virtual void stop(); + + virtual void onNewData(const uint8_t *data, size_t size, double timestamp); + virtual void onDiscontinuity(); + + ConnectedAudioOutputs getConnectedOutputs() const; + + private: + AudioOutput *m_output; + SwrContext *m_swr; + uint8_t *m_buffer; + int m_bufferSize; + int m_inSampleSizeChs; // sampleSize * channelsNo + int m_outSampleSizeChs; +}; + +#endif diff --git a/gizmo/media/decoder.h b/gizmo/media/decoder.h new file mode 100644 index 0000000..f7a52e1 --- /dev/null +++ b/gizmo/media/decoder.h @@ -0,0 +1,29 @@ +#ifndef __DECODER_H__ +#define __DECODER_H__ + +#include +#include + +extern "C" +{ +#include +#include +} + + +class Decoder +{ + public: + virtual ~Decoder() {}; + + virtual void start() = 0; + virtual void stop() = 0; + + virtual bool feed(AVPacket &packet) = 0; + virtual void flush() = 0; + virtual void discontinuity() = 0; + + virtual double getPosition() const = 0; +}; + +#endif diff --git a/gizmo/media/demux.cpp b/gizmo/media/demux.cpp new file mode 100644 index 0000000..b51d698 --- /dev/null +++ b/gizmo/media/demux.cpp @@ -0,0 +1,249 @@ +#include "demux.h" +#include "general/exception.h" +#include +#include + +using namespace std; + + +static void init() +{ + static bool initialized = false; + if (!initialized) + { + av_register_all(); + initialized = true; + } +} + +#define VALIDATE_STREAM_ID(streamId) \ + if (streamId >= m_streamsNo) \ + throw EXCEPTION("stream ID out of range") \ + .module("Demux") \ + .add("id", streamId) \ + .add("range", m_streamsNo) + +Demux::Demux(const string &fileName) : + m_formatContext(NULL), + m_position(0.0), + m_streams(NULL), + m_streamsNo(0) +{ + init(); + + int res = avformat_open_input(&m_formatContext, fileName.c_str(), NULL, NULL); + if (res < 0) + throw EXCEPTION_FFMPEG("can't open multimedia file", res) + .module("Demux", "avformat_open_input") + .file(fileName); + + res = avformat_find_stream_info(m_formatContext, NULL); + if (res < 0) + throw EXCEPTION_FFMPEG("can't read multimedia file", res) + .module("Demux", "avformat_find_stream_info") + .file(fileName); + + m_streamsNo = m_formatContext->nb_streams; + m_streams = new Demux::Stream[m_streamsNo]; + m_streamsInfo.reserve(m_streamsNo); + + for (unsigned i=0; istreams[i]; + StreamFormat info(i, stream); + m_streamsInfo.push_back(info); + + m_streams[i].timeBase = + (double)stream->time_base.num / (double)stream->time_base.den; + } +} + +Demux::~Demux() +{ + delete [] m_streams; + m_streams = NULL; + avformat_close_input(&m_formatContext); +} + +const StreamsFormat &Demux::getStreamsInfo() const +{ + return m_streamsInfo; +} + +AVStream *Demux::getStreamRawData(unsigned streamId) const +{ + VALIDATE_STREAM_ID(streamId); + return m_formatContext->streams[streamId]; +} + +void Demux::connectDec(Decoder *dec, unsigned streamId) +{ + VALIDATE_STREAM_ID(streamId); + m_streams[streamId].connectDecoder(dec); +} + +void Demux::disconnectDec(Decoder *dec, unsigned streamId) +{ + VALIDATE_STREAM_ID(streamId); + + if (!m_streams[streamId].disconnectDecoder(dec)) + { + throw EXCEPTION("decoder is not connected to this demux") + .module("Demux"); + } +} + +void Demux::disconnectAllDec(Decoder *dec) +{ + bool disconnected = false; + + for (unsigned i = 0; i < m_streamsNo; i++) + disconnected |= m_streams[i].disconnectDecoder(dec); + + if (!disconnected) + throw EXCEPTION("decoder is not connected to this demux") + .module("Demux"); +} + +void Demux::start() +{ + for (unsigned i = 0; i < m_streamsNo; i++) + { + for (Decoder *dec : m_streams[i].decoders) + dec->start(); + } +} + +void Demux::stop() +{ + for (unsigned i = 0; i < m_streamsNo; i++) + { + for (Decoder *dec : m_streams[i].decoders) + dec->stop(); + } +} + +bool Demux::step() +{ + AVPacket packet; + av_init_packet(&packet); + + int res = av_read_frame(m_formatContext, &packet); + + if (res >= 0) + { + unique_ptr + scopedPacketUnrefGuard(&packet, &av_packet_unref); + + unsigned streamID = packet.stream_index; + if (streamID < m_streamsNo) + { + Demux::Stream &stream = m_streams[streamID]; + + for (Decoder *dec : stream.decoders) + dec->feed(packet); + + m_position = (double)packet.pts * stream.timeBase; + } + + return true; + } + else if (res == AVERROR_EOF) + { + flush(); + notifyDiscontinuity(); + return false; + } + else + { + throw EXCEPTION_FFMPEG("can't read input stream", res) + .module("Demux", "av_read_frame"); + } +} + +double Demux::getPosition() const +{ + return m_position; +} + +double Demux::getDuration() const +{ + if (m_formatContext && (m_formatContext->duration > 0)) + return (double)m_formatContext->duration / (double)AV_TIME_BASE; + else + return 0.0; +} + +void Demux::seek(double timestamp) +{ + int64_t ts = timestamp * AV_TIME_BASE; + int res = av_seek_frame(m_formatContext, -1, ts, 0); + if (res < 0) + throw EXCEPTION_FFMPEG("can't seek", res) + .module("Demux", "av_seek_frame") + .time(timestamp); + + m_position = timestamp; +} + +void Demux::notifyDiscontinuity() +{ + for (unsigned i = 0; i < m_streamsNo; i++) + { + for (Decoder *dec : m_streams[i].decoders) + dec->discontinuity(); + } +} + +void Demux::flush() +{ + for (unsigned i = 0; i < m_streamsNo; i++) + { + for (Decoder *dec : m_streams[i].decoders) + dec->flush(); + } +} + +Demux::ConnectedOutputs Demux::getConnectedOutputs() const +{ + ConnectedOutputs res; + for (unsigned i = 0; i < m_streamsNo; i++) + { + for (Decoder *dec : m_streams[i].decoders) + { + res.push_back(ConnectedOutput("stream" + to_string(i), dec)); + } + } + return res; +} + +/*** Demux::Stream ***/ + +Demux::Stream::Stream() : timeBase(0.0) +{} + +void Demux::Stream::connectDecoder(Decoder *decoder) +{ + for (Decoder *dec : decoders) + { + if (dec == decoder) + throw EXCEPTION("decoder already connected to this stream") + .module("Demux", "Stream"); + } + + decoders.push_back(decoder); +} + +bool Demux::Stream::disconnectDecoder(Decoder *decoder) +{ + for (Decoders::iterator it = decoders.begin(); it != decoders.end(); ++it) + { + if (*it == decoder) + { + decoders.erase(it); + return true; + } + } + + return false; +} diff --git a/gizmo/media/demux.h b/gizmo/media/demux.h new file mode 100644 index 0000000..aa2a55b --- /dev/null +++ b/gizmo/media/demux.h @@ -0,0 +1,66 @@ +#ifndef __DEMUX_H__ +#define __DEMUX_H__ + +#include "decoder.h" +#include "stream.h" +#include +#include +#include +#include + +struct AVFormatContext; +struct AVStream; + + +class Demux +{ + public: + Demux(const std::string &fileName); + ~Demux(); + + const StreamsFormat &getStreamsInfo() const; + + void connectDec(Decoder *dec, unsigned streamId); + void disconnectDec(Decoder *dec, unsigned streamId); + void disconnectAllDec(Decoder *dec); + + double getPosition() const; + double getDuration() const; + + void start(); + void stop(); + + bool step(); + void seek(double timestamp); + + void notifyDiscontinuity(); + void flush(); + + AVStream *getStreamRawData(unsigned streamId) const; + + typedef std::tuple ConnectedOutput; + typedef std::vector ConnectedOutputs; + ConnectedOutputs getConnectedOutputs() const; + + private: + AVFormatContext *m_formatContext; + StreamsFormat m_streamsInfo; + std::atomic m_position; + + private: + struct Stream + { + typedef std::vector Decoders; + Decoders decoders; + double timeBase; + + Stream(); + void connectDecoder(Decoder *decoder); + bool disconnectDecoder(Decoder *decoder); + }; + + Stream *m_streams; + unsigned m_streamsNo; +}; + +#endif diff --git a/gizmo/media/speechrec.cpp b/gizmo/media/speechrec.cpp new file mode 100644 index 0000000..bf2f5b2 --- /dev/null +++ b/gizmo/media/speechrec.cpp @@ -0,0 +1,204 @@ +#include "speechrec.h" +#include "general/exception.h" +#include +#include + +using namespace std; + + +SpeechRecognition::SpeechRecognition() : + m_ps(NULL), + m_config(NULL), + m_utteranceStarted(false), + m_framePeriod(0.0), + m_deltaTime(-1.0), + m_minProb(1.0f), + m_minLen(0) +{ + m_config = cmd_ln_parse_r(NULL, ps_args(), 0, NULL, TRUE); + if (m_config == NULL) + throw EXCEPTION("can't init Sphinx configuration") + .module("SpeechRecognition", "cmd_ln_parse_r"); +} + +SpeechRecognition::~SpeechRecognition() +{ + cmd_ln_free_r(m_config); +} + +void SpeechRecognition::setParam(const string &key, const string &val) +{ + arg_t const *args = ps_args(); + + for (size_t i = 0; args[i].name != NULL; i++) + { + if (key == args[i].name) + { + int type = args[i].type; + if (type & ARG_INTEGER) + cmd_ln_set_int_r(m_config, key.c_str(), atol(val.c_str())); + else if (type & ARG_FLOATING) + cmd_ln_set_float_r(m_config, key.c_str(), atof(val.c_str())); + else if (type & ARG_STRING) + cmd_ln_set_str_r(m_config, key.c_str(), val.c_str()); + else if (type & ARG_BOOLEAN) + cmd_ln_set_boolean_r(m_config, key.c_str(), val.c_str()); + else + throw EXCEPTION("invalid parameter type") + .module("SpeechRecognition", "setParameter") + .add("parameter", key) + .add("value", val) + .add("type", type); + + return; + } + } + + throw EXCEPTION("parameter not supported") + .module("SpeechRecognition", "setParameter") + .add("parameter", key) + .add("value", val); +} + +void SpeechRecognition::connectWordsCallback(WordsCallback callback) +{ + m_wordsCb = callback; +} + +void SpeechRecognition::setMinWordProb(float minProb) +{ + m_minProb = minProb; +} + +void SpeechRecognition::setMinWordLen(unsigned minLen) +{ + m_minLen = minLen; +} + +void SpeechRecognition::start() +{ + if ((m_ps = ps_init(m_config)) == NULL) + throw EXCEPTION("can't init Sphinx engine") + .module("SpeechRecognition", "ps_init"); + + int32_t frate = cmd_ln_int32_r(m_config, "-frate"); + m_framePeriod = 1.0 / (double)frate; + + if (frate == 0) + throw EXCEPTION("can't get frame rate value") + .module("SpeechRecognition", "cmd_ln_int32_r"); + + if (ps_start_utt(m_ps)) + throw EXCEPTION("can't start speech recognition") + .module("SpeechRecognition", "ps_start_utt"); + + m_utteranceStarted = false; +} + +void SpeechRecognition::stop() +{ + if (m_ps) + { + if (ps_end_utt(m_ps)) + throw EXCEPTION("can't stop speech recognition") + .module("SpeechRecognition", "ps_end_utt"); + + if (m_utteranceStarted) + { + parseUtterance(); + m_utteranceStarted = false; + } + + ps_free(m_ps); + m_ps = NULL; + } +} + +void SpeechRecognition::onNewData(const uint8_t *data, size_t size, double timestamp) +{ + if (m_deltaTime < 0.0) + m_deltaTime = timestamp; + + int no = ps_process_raw(m_ps, (const int16*)data, size/2, FALSE, FALSE); + if (no < 0) + throw EXCEPTION("speech recognition error") + .module("SpeechRecognition", "ps_process_raw"); + + uint8 inSpeech = ps_get_in_speech(m_ps); + if (inSpeech && !m_utteranceStarted) + { + m_utteranceStarted = true; + } + if (!inSpeech && m_utteranceStarted) + { + if (ps_end_utt(m_ps)) + throw EXCEPTION("can't end utterance") + .module("SpeechRecognition", "ps_end_utt"); + + parseUtterance(); + + if (ps_start_utt(m_ps)) + throw EXCEPTION("can't start utterance") + .module("SpeechRecognition", "ps_start_utt"); + + m_utteranceStarted = false; + } +} + +void SpeechRecognition::onDiscontinuity() +{ + if (ps_end_utt(m_ps)) + throw EXCEPTION("can't stop speech recognition") + .module("SpeechRecognition", "ps_end_utt"); + + if (m_utteranceStarted) + parseUtterance(); + + m_deltaTime = -1.0; + if (ps_start_stream(m_ps)) + { + throw EXCEPTION("can't reset speech recognition engine") + .module("SpeechRecognition", "sphinx", "ps_start_stream"); + } + + if (ps_start_utt(m_ps)) + throw EXCEPTION("can't start speech recognition") + .module("SpeechRecognition", "ps_start_utt"); + + m_utteranceStarted = false; +} + +void SpeechRecognition::parseUtterance() +{ + const char *text; + int begin, end, pprob; + string word; + + for (ps_seg_t *it=ps_seg_iter(m_ps); it!=NULL; it=ps_seg_next(it)) + { + text = ps_seg_word(it); + if (text && text[0] != '<' && text[0] != '[') + { + word = text; + if ((word.size() > 3) && (word.back() == ')')) + { + size_t pos = word.size() - 2; + while (word[pos] >= '0' && word[pos] <= '9' && pos > 0) + pos--; + + if (pos <= word.size()-3 && word[pos] == '(') + word.resize(pos); + } + + ps_seg_frames(it, &begin, &end); + double time = ((double)begin+(double)end) * m_framePeriod / 2.0; + + pprob = ps_seg_prob(it, NULL, NULL, NULL); + float prob = logmath_exp(ps_get_logmath(m_ps), pprob); + + if (m_wordsCb && word.size() >= m_minLen && prob >= m_minProb) + m_wordsCb(word, time + m_deltaTime); + } + } +} + diff --git a/gizmo/media/speechrec.h b/gizmo/media/speechrec.h new file mode 100644 index 0000000..f760969 --- /dev/null +++ b/gizmo/media/speechrec.h @@ -0,0 +1,46 @@ +#ifndef __SPHINX_H__ +#define __SPHINX_H__ + +#include "audioout.h" +#include "text/words.h" +#include +#include +#include + + +class SpeechRecognition : public AudioOutput +{ + public: + SpeechRecognition(); + virtual ~SpeechRecognition(); + + virtual void start(); + virtual void stop(); + + void setParam(const std::string &key, const std::string &val); + + void connectWordsCallback(WordsCallback callback); + void setMinWordProb(float minProb); + void setMinWordLen(unsigned minLen); + + virtual void onNewData(const uint8_t *data, size_t size, double timestamp); + virtual void onDiscontinuity(); + + private: + void parseUtterance(); + + private: + ps_decoder_t *m_ps; + cmd_ln_t *m_config; + + bool m_utteranceStarted; + + double m_framePeriod; + double m_deltaTime; + + WordsCallback m_wordsCb; + float m_minProb; + unsigned m_minLen; +}; + +#endif diff --git a/gizmo/media/stream.cpp b/gizmo/media/stream.cpp new file mode 100644 index 0000000..44365b2 --- /dev/null +++ b/gizmo/media/stream.cpp @@ -0,0 +1,228 @@ +#include "stream.h" +#include +#include + +using namespace std; + + +static const char *channelIdToName(uint64_t id) +{ + switch (id) + { + case AV_CH_FRONT_LEFT: return "front left"; + case AV_CH_FRONT_RIGHT: return "front right"; + case AV_CH_FRONT_CENTER: return "front center"; + case AV_CH_LOW_FREQUENCY: return "subwoofer"; + case AV_CH_BACK_LEFT: return "back left"; + case AV_CH_BACK_RIGHT: return "back right"; + case AV_CH_FRONT_LEFT_OF_CENTER: return "front left of center"; + case AV_CH_FRONT_RIGHT_OF_CENTER: return "front right of center"; + case AV_CH_BACK_CENTER: return "back center"; + case AV_CH_SIDE_LEFT: return "side left"; + case AV_CH_SIDE_RIGHT: return "side right"; + case AV_CH_TOP_CENTER: return "top center"; + case AV_CH_TOP_FRONT_LEFT: return "top front left"; + case AV_CH_TOP_FRONT_CENTER: return "top front center"; + case AV_CH_TOP_FRONT_RIGHT: return "top front right"; + case AV_CH_TOP_BACK_LEFT: return "top back left"; + case AV_CH_TOP_BACK_CENTER: return "top back center"; + case AV_CH_TOP_BACK_RIGHT: return "top back right"; + case AV_CH_STEREO_LEFT: return "stereo left"; + case AV_CH_STEREO_RIGHT: return "stereo right"; + case AV_CH_WIDE_LEFT: return "wide left"; + case AV_CH_WIDE_RIGHT: return "wide right"; + case AV_CH_SURROUND_DIRECT_LEFT: return "surround direct left"; + case AV_CH_SURROUND_DIRECT_RIGHT: return "surround direct right"; + case AV_CH_LOW_FREQUENCY_2: return "second subwoofer"; + default: return NULL; + } +} + +static const char *sampleFormatToName(AVSampleFormat format) +{ + switch (format) + { + case AV_SAMPLE_FMT_NONE: return "NONE"; + case AV_SAMPLE_FMT_U8: return "U8"; + case AV_SAMPLE_FMT_S16: return "S16"; + case AV_SAMPLE_FMT_S32: return "S32"; + case AV_SAMPLE_FMT_FLT: return "FLT"; + case AV_SAMPLE_FMT_DBL: return "DBL"; + case AV_SAMPLE_FMT_U8P: return "U8P"; + case AV_SAMPLE_FMT_S16P: return "S16P"; + case AV_SAMPLE_FMT_S32P: return "S32P"; + case AV_SAMPLE_FMT_FLTP: return "FLTP"; + case AV_SAMPLE_FMT_DBLP: return "DBLP"; + case AV_SAMPLE_FMT_S64: return "S64"; + case AV_SAMPLE_FMT_S64P: return "S64P"; + default: return NULL; + } +} + + +static AVCodecContext *makeCodecContext(const AVStream *stream) +{ + if (stream->codecpar == NULL) + return NULL; + + AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (codec == NULL) + return NULL; + + AVCodecContext *ctx = avcodec_alloc_context3(codec); + if (ctx == NULL) + return NULL; + + avcodec_parameters_to_context(ctx, stream->codecpar); + avcodec_open2(ctx, codec, NULL); + return ctx; +} + + +/*** StreamFormat ***/ + +StreamFormat::StreamFormat() +{} + +StreamFormat::StreamFormat(unsigned no, const AVStream *stream) : no(no) +{ + if (stream == NULL) + return; + + if (AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id)) + { + this->codec = codec->name ? codec->name : ""; + } + + frameRate = 0.0; + if (!(stream->avg_frame_rate.den == 0.0)) + frameRate = (double)stream->avg_frame_rate.num / stream->avg_frame_rate.den; + + AVDictionaryEntry* lang = av_dict_get(stream->metadata, "language", NULL, 0); + if (lang && lang->value) + this->lang = lang->value; + + AVDictionaryEntry *title = av_dict_get(stream->metadata, "title", NULL, 0); + if (title && title->value) + this->title = title->value; + + if (stream->codecpar) + { + AVCodecContext *ctx = NULL; + + switch (stream->codecpar->codec_type) + { + case AVMEDIA_TYPE_VIDEO: + this->type = "video"; + break; + + case AVMEDIA_TYPE_AUDIO: + this->type = "audio"; + ctx = makeCodecContext(stream); + audio = AudioFormat(ctx); + break; + + case AVMEDIA_TYPE_SUBTITLE: + this->type = "subtitle"; + ctx = makeCodecContext(stream); + + if (ctx && ctx->codec_descriptor) + { + int props = ctx->codec_descriptor->props; + bool txt = props & AV_CODEC_PROP_TEXT_SUB; + bool bmp = props & AV_CODEC_PROP_BITMAP_SUB; + + if (txt && !bmp) this->type += "/text"; + if (bmp && !txt) this->type += "/bitmap"; + } + break; + + default: + this->type = "other"; + } + + avcodec_free_context(&ctx); + } +} + +string StreamFormat::toString() const +{ + stringstream ss; + ss << " 0.0) ss << ", frameRate=" << frameRate; + if (type == "audio") ss << ", audio=" << audio.toString(); + ss << ">"; + return ss.str(); +} + + +/*** AudioFormat ***/ + +AudioFormat::AudioFormat(const AVCodecContext *ctx) +{ + sampleFormat = AV_SAMPLE_FMT_NONE; + sampleRate = 0; + channelsNo = 0; + channelLayout = 0; + + if (ctx) + { + sampleFormat = ctx->sample_fmt; + sampleRate = ctx->sample_rate; + channelsNo = ctx->channels; + channelLayout = ctx->channel_layout; + + if (channelLayout == 0) + channelLayout = av_get_default_channel_layout(ctx->channels); + } +} + +AudioFormat::AudioFormat(AVSampleFormat sampleFormat, unsigned sampleRate, + uint64_t channelLayout) : + sampleFormat(sampleFormat), + sampleRate(sampleRate), + channelsNo(0), + channelLayout(channelLayout) +{ + for (uint64_t id = 1; id <= channelLayout; id <<= 1) + { + if (id & channelLayout) + channelsNo++; + } +} + +unsigned AudioFormat::getSampleSize() const +{ + return av_get_bytes_per_sample(sampleFormat); +} + +map AudioFormat::getChannelNames() const +{ + map res; + for (uint64_t no = 1; no <= channelLayout; no <<= 1) + { + if (no & channelLayout) + { + const char *name = channelIdToName(no); + if (name) + res[no] = name; + } + } + + return res; +} + +string AudioFormat::toString() const +{ + stringstream ss; + ss << ""; + return ss.str(); +} + diff --git a/gizmo/media/stream.h b/gizmo/media/stream.h new file mode 100644 index 0000000..0741054 --- /dev/null +++ b/gizmo/media/stream.h @@ -0,0 +1,48 @@ +#ifndef __STREAMINFO_H__ +#define __STREAMINFO_H__ + +#include +#include +#include + +extern "C" +{ +#include +} + + +struct AudioFormat +{ + AVSampleFormat sampleFormat; + unsigned sampleRate; + unsigned channelsNo; + uint64_t channelLayout; + + AudioFormat(const AVCodecContext *codecContext = NULL); + AudioFormat(AVSampleFormat sampleFormat, unsigned sampleRate, + uint64_t channelLayout=0); + + unsigned getSampleSize() const; + std::map getChannelNames() const; + std::string toString() const; +}; + +struct StreamFormat +{ + unsigned no; + std::string type; + std::string codec; + std::string lang; + std::string title; + double frameRate; + + AudioFormat audio; + + StreamFormat(); + StreamFormat(unsigned no, const AVStream *stream); + std::string toString() const; +}; + +typedef std::vector StreamsFormat; + +#endif diff --git a/gizmo/media/subdec.cpp b/gizmo/media/subdec.cpp new file mode 100644 index 0000000..3b10ee8 --- /dev/null +++ b/gizmo/media/subdec.cpp @@ -0,0 +1,225 @@ +#include "subdec.h" +#include "demux.h" +#include "general/exception.h" +#include +#include + +using namespace std; + + +SubtitleDec::SubtitleDec(const Demux *demux, unsigned streamId) + : SubtitleDec(demux->getStreamRawData(streamId)) +{ +} + +SubtitleDec::SubtitleDec(AVStream *stream) : + m_codec(NULL), + m_codecPar(stream->codecpar), + m_codecCtx(NULL), + m_minWordLen(0), + m_timeBase(0.0), + m_position(0.0) +{ + m_codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (m_codec == NULL) + throw EXCEPTION("can't find suitable audio codec") + .module("SubtitleDec", "avcodec_find_decoder"); + + m_timeBase = (double)stream->time_base.num/(double)stream->time_base.den; +} + +SubtitleDec::~SubtitleDec() +{ +} + +void SubtitleDec::start() +{ + m_codecCtx = avcodec_alloc_context3(m_codec); + + m_codec = avcodec_find_decoder(m_codecPar->codec_id); + if (m_codec == NULL) + throw EXCEPTION("can't find suitable subtitle decoder") + .module("SubtitleDec", "avcodec_find_decoder"); + + if (avcodec_parameters_to_context(m_codecCtx, m_codecPar) < 0) + throw EXCEPTION("can't set subtitle codec context") + .module("SubtitleDec", "avcodec_parameters_to_context"); + + if (m_codecCtx->codec_type != AVMEDIA_TYPE_SUBTITLE) + throw EXCEPTION("this is not subtitle stream") + .module("SubtitleDec"); + + AVDictionary *options = NULL; + av_dict_set(&options, "sub_text_format", "ass", 0); + + if (!m_encoding.empty()) + { + av_dict_set(&options, "sub_charenc_mode", "pre_decoder", 0); + av_dict_set(&options, "sub_charenc", m_encoding.c_str(), 0); + } + + int res = avcodec_open2(m_codecCtx, m_codec, &options); + av_dict_free(&options); + + if (res < 0) + throw EXCEPTION_FFMPEG("can't open subtitle stream", res) + .module("SubtitleDec", "avcodec_open2"); +} + +void SubtitleDec::stop() +{ + avcodec_close(m_codecCtx); + avcodec_free_context(&m_codecCtx); +} + +bool SubtitleDec::feed(AVPacket &packet) +{ + if (!m_subsCb && !m_wordsCb) + return false; + + AVSubtitle sub; + int gotSub; + + int len = avcodec_decode_subtitle2(m_codecCtx, &sub, &gotSub, &packet); + if (len < 0) + throw EXCEPTION_FFMPEG("subtitle decoder failed", len) + .module("SubtitleDec", "decode", "avcodec_decode_subtitle2") + .time((double)packet.pts * m_timeBase); + + if (!gotSub) + return false; + + unique_ptr + scopedSubFreeGuard(&sub, &avsubtitle_free); + + m_position = (double)packet.pts * m_timeBase; + double duration = (double)packet.duration * m_timeBase; + + gotSub = feedOutput(sub, duration); + return gotSub; +} + +bool SubtitleDec::feedOutput(AVSubtitle &sub, double duration) +{ + bool gotSub = false; + + double begin = m_position + ((double)sub.start_display_time / 1000.0); + double end = sub.end_display_time ? + m_position + (double)sub.end_display_time / 1000.0 : + begin + duration; + + for (unsigned i = 0; i < sub.num_rects; i++) + { + AVSubtitleRect *rect = sub.rects[i]; + const char *text = rect->type == SUBTITLE_ASS ? rect->ass : rect->text; + + if (text) + { + gotSub = true; + + if (m_subsCb) + m_subsCb(begin, end, text); + + if (m_wordsCb) + feedWordsOutput(begin, end, text); + } + } + + return gotSub; +} + +void SubtitleDec::feedWordsOutput(double begin, double end, const char *data) +{ + const char *text = data; + unsigned commas = 0; + + while (*text != '\0') + { + if (*text++ == ',' && ++commas >= 8) + break; + } + + if (commas < 8) + text = data; + + double delta = (end - begin) / (double) strlen(text); + const char *p = text; + + while (*p != '\0') + { + string word; + size_t beg = p - text; + size_t end = p - text; + + while (*p != '\0') + { + if (p[0] == '\\' && (p[1] == 'n' || p[1] == 'N')) + { + p += 2; + break; + } + else if (p[0] == '{' && p[1] == '\\') + { + for (p += 2; *p != '}' && *p != '\0'; p++); + break; + } + else if (strchr(" \t\n.,!?[]{}<>|\\/\"#$%-+`", *p)) + { + p++; + break; + } + else + { + word += *p; + } + + p++; + end++; + } + + if (word.size() >= m_minWordLen) + { + double time = begin + delta * (double) (beg + end) / 2.0; + m_wordsCb(word, time); + } + } +} + +void SubtitleDec::flush() +{ + AVPacket packet; + av_init_packet(&packet); + packet.data = NULL; + packet.size = 0; + feed(packet); +} + +void SubtitleDec::discontinuity() +{ +} + +double SubtitleDec::getPosition() const +{ + return m_position; +} + +void SubtitleDec::connectSubsCallback(SubsCallback callback) +{ + m_subsCb = callback; +} + +void SubtitleDec::connectWordsCallback(WordsCallback callback) +{ + m_wordsCb = callback; +} + +void SubtitleDec::setMinWordLen(unsigned minWordLen) +{ + m_minWordLen = minWordLen; +} + +void SubtitleDec::setEncoding(const string &encoding) +{ + m_encoding = encoding; +} + diff --git a/gizmo/media/subdec.h b/gizmo/media/subdec.h new file mode 100644 index 0000000..bb69b09 --- /dev/null +++ b/gizmo/media/subdec.h @@ -0,0 +1,66 @@ +#ifndef __SUBTITLE_DECODER_H__ +#define __SUBTITLE_DECODER_H__ + +#include "decoder.h" +#include "text/words.h" +#include +#include + +extern "C" +{ +#include +#include +} + +class Demux; + + +class SubtitleDec : public Decoder +{ + public: + typedef std::function + SubsCallback; + + public: + SubtitleDec(const Demux *demux, unsigned streamId); + SubtitleDec(AVStream *stream); + virtual ~SubtitleDec(); + + virtual void start(); + virtual void stop(); + + virtual bool feed(AVPacket &packet); + virtual void flush(); + virtual void discontinuity(); + + virtual double getPosition() const; + + void connectSubsCallback(SubsCallback callback); + void connectWordsCallback(WordsCallback callback); + + void setMinWordLen(unsigned minLen); + void setEncoding(const std::string &encoding); + + + private: + bool feedOutput(AVSubtitle &sub, double duration); + void feedWordsOutput(double begin, double end, const char *text); + + private: + AVCodec *m_codec; + AVCodecParameters *m_codecPar; + AVCodecContext *m_codecCtx; + + SubsCallback m_subsCb; + WordsCallback m_wordsCb; + unsigned m_minWordLen; + + double m_timeBase; + double m_position; + std::string m_encoding; +}; + +#endif diff --git a/gizmo/python/correlator.cpp b/gizmo/python/correlator.cpp new file mode 100644 index 0000000..a7d544f --- /dev/null +++ b/gizmo/python/correlator.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "correlator.h" +#include "math/line.h" + +namespace py = pybind11; + + +void initCorrelatorWrapper(py::module &m) +{ + /*** class Correlator ***/ + py::class_ correlator(m, "Correlator", py::dynamic_attr()); + correlator.def(py::init()); + correlator.def("connectStatsCallback", &Correlator::connectStatsCallback); + correlator.def("start", &Correlator::start); + correlator.def("stop", &Correlator::stop); + correlator.def("isRunning", &Correlator::isRunning); + correlator.def("isDone", &Correlator::isDone); + correlator.def("getProgress", &Correlator::getProgress); + correlator.def("pushSubWord", &Correlator::pushSubWord); + correlator.def("pushRefWord", &Correlator::pushRefWord); + correlator.def("getSubs", &Correlator::getSubs); + correlator.def("getRefs", &Correlator::getRefs); + + /*** struct CorrelationStats ***/ + py::class_ corrStats(m, "CorrelationStats"); + corrStats.def(py::init<>()); + corrStats.def_readonly("correlated", &CorrelationStats::correlated); + corrStats.def_readonly("factor", &CorrelationStats::factor); + corrStats.def_readonly("points", &CorrelationStats::points); + corrStats.def_readonly("maxDistance", &CorrelationStats::maxDistance); + corrStats.def_readonly("formula", &CorrelationStats::formula); + corrStats.def("__repr__", &CorrelationStats::toString); + corrStats.def("__str__", [](const CorrelationStats &s) { + return s.toString("", ""); + }); + + /*** class Line ***/ + py::class_ line(m, "Line"); + line.def(py::init(), py::arg("a") = 0.0f, py::arg("b") = 0.0f); + line.def_readwrite("a", &Line::a); + line.def_readwrite("b", &Line::b); + line.def("getX", &Line::getX); + line.def("getY", &Line::getY); + line.def("__repr__", &Line::toString); +} diff --git a/gizmo/python/extractor.cpp b/gizmo/python/extractor.cpp new file mode 100644 index 0000000..fb58f0c --- /dev/null +++ b/gizmo/python/extractor.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +#include "extractor.h" + +namespace py = pybind11; + + +void initExtractorWrapper(py::module &m) +{ + py::class_ extractor(m, "Extractor", py::dynamic_attr()); + extractor.def(py::init()); + extractor.def("getStreamsInfo", &Extractor::getStreamsInfo); + extractor.def("selectTimeWindow", &Extractor::selectTimeWindow); + extractor.def("connectEosCallback", &Extractor::connectEosCallback); + extractor.def("connectErrorCallback", &Extractor::connectErrorCallback); + extractor.def("start", &Extractor::start); + extractor.def("stop", &Extractor::stop); + extractor.def("isRunning", &Extractor::isRunning); +} diff --git a/gizmo/python/general.cpp b/gizmo/python/general.cpp new file mode 100644 index 0000000..7a6533e --- /dev/null +++ b/gizmo/python/general.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "general/exception.h" +#include "general/logger.h" + +namespace py = pybind11; + +using namespace std; + + +void initGeneralWrapper(py::module &m) +{ + /*** class Exception ***/ + py::register_exception(m, "Error"); + + py::class_ error(m, "Exception"); + error.def_property_readonly("message", &Exception::message); + error.def_property_readonly("fields", &Exception::fields); + error.def("__repr__", &Exception::what); + + + /*** logger configuration ***/ + m.def("setLoggerCallback", &setLoggerCallback); + m.def("setDebugLevel", setDebugLevel); +} diff --git a/gizmo/python/media.cpp b/gizmo/python/media.cpp new file mode 100644 index 0000000..028488f --- /dev/null +++ b/gizmo/python/media.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include "media/demux.h" +#include "media/subdec.h" +#include "media/audiodec.h" +#include "media/audiodmx.h" +#include "media/audioresampler.h" +#include "media/audioout.h" +#include "media/speechrec.h" + +namespace py = pybind11; + + +void initMediaWrapper(py::module &m) +{ + /*** class Demux ***/ + py::class_ demux(m, "Demux", py::dynamic_attr()); + demux.def(py::init()); + demux.def("getStreamsInfo", &Demux::getStreamsInfo); + demux.def("connectDec", &Demux::connectDec); + demux.def("disconnectDec", &Demux::disconnectDec); + demux.def("disconnectAllDec", &Demux::disconnectAllDec); + demux.def("getPosition", &Demux::getPosition); + demux.def("getDuration", &Demux::getDuration); + demux.def("start", &Demux::start); + demux.def("stop", &Demux::stop); + demux.def("step", &Demux::step); + demux.def("seek", &Demux::seek); + demux.def("notifyDiscontinuity", &Demux::notifyDiscontinuity); + demux.def("getConnectedOutputs", &Demux::getConnectedOutputs); + + /*** interface Decoder ***/ + py::class_ decoder(m, "Decoder", py::dynamic_attr()); + decoder.def("start", &Decoder::start); + decoder.def("stop", &Decoder::stop); + decoder.def("getPosition", &Decoder::getPosition); + + /*** class SubtitleDec ***/ + py::class_ subDec(m, "SubtitleDec", decoder, py::dynamic_attr()); + subDec.def(py::init()); + subDec.def("setMinWordLen", &SubtitleDec::setMinWordLen); + subDec.def("setEncoding", &SubtitleDec::setEncoding); + subDec.def("connectSubsCallback", + [] (SubtitleDec &s, SubtitleDec::SubsCallback cb, py::handle dst) { + (void) dst; + s.connectSubsCallback(cb); + }, py::arg("callback"), py::arg("dst") = NULL); + subDec.def("connectWordsCallback", + [] (SubtitleDec &s, WordsCallback cb, py::handle dst) { + (void) dst; + s.connectWordsCallback(cb); + }, py::arg("callback"), py::arg("dst") = NULL); + + /*** class AudioDec ***/ + py::class_ audioDec(m, "AudioDec", decoder, py::dynamic_attr()); + audioDec.def(py::init()); + audioDec.def("getFormat", &AudioDec::getFormat); + audioDec.def("connectOutput", &AudioDec::connectOutput); + audioDec.def("getConnectedOutputs", &AudioDec::getConnectedOutputs); + + /*** interface AudioOutput ***/ + py::class_ audioOut(m, "AudioOutput", py::dynamic_attr()); + + /*** class AudioResampler ***/ + py::class_ audioRes(m, "AudioResampler", audioOut, + py::dynamic_attr()); + audioRes.def(py::init<>()); + audioRes.def("setParams", &AudioResampler::setParams, + py::arg("input"), py::arg("output"), py::arg("mixMap"), + py::arg("bufferSize") = 32*1024); + audioRes.def("connectOutput", &AudioResampler::connectOutput); + audioRes.def("getConnectedOutputs", &AudioResampler::getConnectedOutputs); + + /*** class AudioDemux ***/ + py::class_ audioDemux(m, "AudioDemux", audioOut, + py::dynamic_attr()); + audioDemux.def(py::init<>()); + audioDemux.def("setOutputFormat", + (void (AudioDemux::*)(unsigned, unsigned)) + &AudioDemux::setOutputFormat); + audioDemux.def("setOutputFormat", + (void (AudioDemux::*)(const AudioFormat&)) + &AudioDemux::setOutputFormat); + audioDemux.def("connectOutputChannel", &AudioDemux::connectOutputChannel); + audioDemux.def("getConnectedOutputs", &AudioDemux::getConnectedOutputs); + + /*** class SpeechRecognition ***/ + py::class_ speechRec(m, "SpeechRecognition", audioOut, + py::dynamic_attr()); + speechRec.def(py::init<>()); + speechRec.def("setParam", &SpeechRecognition::setParam); + speechRec.def("setMinWordProb", &SpeechRecognition::setMinWordProb); + speechRec.def("setMinWordLen", &SpeechRecognition::setMinWordLen); + speechRec.def("connectWordsCallback", + [] (SpeechRecognition &sr, WordsCallback cb, py::handle dst) { + (void) dst; + sr.connectWordsCallback(cb); + }, py::arg("callback"), py::arg("dst") = NULL); +} diff --git a/gizmo/python/stream.cpp b/gizmo/python/stream.cpp new file mode 100644 index 0000000..9c11929 --- /dev/null +++ b/gizmo/python/stream.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "media/stream.h" + +namespace py = pybind11; + + +void initStreamWrapper(py::module &m) +{ + /*** struct StreamFormat ***/ + py::class_ streamFormat(m, "StreamFormat"); + streamFormat.def_readonly("no", &StreamFormat::no); + streamFormat.def_readonly("type", &StreamFormat::type); + streamFormat.def_readonly("codec", &StreamFormat::codec); + streamFormat.def_readonly("lang", &StreamFormat::lang); + streamFormat.def_readonly("title", &StreamFormat::title); + streamFormat.def_readonly("frameRate", &StreamFormat::frameRate); + streamFormat.def_readonly("audio", &StreamFormat::audio); + streamFormat.def("__repr__", &StreamFormat::toString); + + /*** struct AudioFormat ***/ + py::class_ audioFormat(m, "AudioFormat"); + audioFormat.def(py::init()); + audioFormat.def_readwrite("sampleFormat", &AudioFormat::sampleFormat); + audioFormat.def_readwrite("sampleRate", &AudioFormat::sampleRate); + audioFormat.def_readwrite("channelsNo", &AudioFormat::channelsNo); + audioFormat.def_readwrite("channelLayout", &AudioFormat::channelLayout); + audioFormat.def("getSampleSize", &AudioFormat::getSampleSize); + audioFormat.def("getChannelNames", &AudioFormat::getChannelNames); + audioFormat.def("__repr__", &AudioFormat::toString); + + /*** enum AVSampleFormat ***/ + py::enum_ avSampleFormat(m, "AVSampleFormat"); + avSampleFormat.value("NONE", AV_SAMPLE_FMT_NONE); + avSampleFormat.value("U8", AV_SAMPLE_FMT_U8); + avSampleFormat.value("S16", AV_SAMPLE_FMT_S16); + avSampleFormat.value("S32", AV_SAMPLE_FMT_S32); + avSampleFormat.value("FLT", AV_SAMPLE_FMT_FLT); + avSampleFormat.value("DBL", AV_SAMPLE_FMT_DBL); + avSampleFormat.value("U8P", AV_SAMPLE_FMT_U8P); + avSampleFormat.value("S16P", AV_SAMPLE_FMT_S16P); + avSampleFormat.value("S32P", AV_SAMPLE_FMT_S32P); + avSampleFormat.value("FLTP", AV_SAMPLE_FMT_FLTP); + avSampleFormat.value("DBLP", AV_SAMPLE_FMT_DBLP); + avSampleFormat.value("S64", AV_SAMPLE_FMT_S64); + avSampleFormat.value("S64P", AV_SAMPLE_FMT_S64P); + avSampleFormat.export_values(); +} diff --git a/gizmo/python/translator.cpp b/gizmo/python/translator.cpp new file mode 100644 index 0000000..93cb3ed --- /dev/null +++ b/gizmo/python/translator.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "text/translator.h" +#include "text/dictionary.h" + +namespace py = pybind11; + + +void initTranslatorWrapper(py::module &m) +{ + py::class_ dictionary(m, "Dictionary", py::dynamic_attr()); + dictionary.def(py::init<>()); + dictionary.def("add", &Dictionary::add); + dictionary.def("size", &Dictionary::size); + dictionary.def("translate", &Dictionary::translate); + + py::class_ translator(m, "Translator", py::dynamic_attr()); + translator.def(py::init()); + translator.def("setMinWordsSim", &Translator::setMinWordsSim); + translator.def("pushWord", &Translator::pushWord); + translator.def("connectWordsCallback", + [] (Translator &tr, WordsCallback cb, py::handle dst) { + (void) dst; + tr.connectWordsCallback(cb); + }, py::arg("callback"), py::arg("dst") = NULL); +} diff --git a/gizmo/python/wrapper.cpp b/gizmo/python/wrapper.cpp new file mode 100644 index 0000000..2d245e1 --- /dev/null +++ b/gizmo/python/wrapper.cpp @@ -0,0 +1,24 @@ +#include + +namespace py = pybind11; + + +void initGeneralWrapper(py::module &); +void initExtractorWrapper(py::module &); +void initMediaWrapper(py::module &); +void initStreamWrapper(py::module &); +void initCorrelatorWrapper(py::module &); +void initTranslatorWrapper(py::module &); + + +PYBIND11_MODULE(gizmo, m) +{ + m.doc() = "subsync synchronization module"; + + initGeneralWrapper(m); + initExtractorWrapper(m); + initMediaWrapper(m); + initStreamWrapper(m); + initCorrelatorWrapper(m); + initTranslatorWrapper(m); +} diff --git a/gizmo/test/catch.hpp b/gizmo/test/catch.hpp new file mode 100644 index 0000000..732a551 --- /dev/null +++ b/gizmo/test/catch.hpp @@ -0,0 +1,13359 @@ +/* + * Catch v2.3.0 + * Generated: 2018-07-23 10:09:14.936841 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 3 +#define CATCH_VERSION_PATCH 0 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions +# pragma GCC diagnostic ignored "-Wparentheses" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +# if __cplusplus >= 201703L +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE + +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + using ITestCasePtr = std::shared_ptr; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include + +namespace Catch { + + class StringData; + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +// end catch_stringref.h +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include +#include +#include +#include +// start catch_stream.h + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + + static void cleanup(); + }; +} + +// end catch_stream.h + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in operator<< from global namespace into Catch namespace + using ::operator<<; + + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template + struct StringMaker { + template + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + typename std::enable_if::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker::type>::type>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast::type>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template + struct StringMaker { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b); + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char c); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker { + static std::string convert(float value); + }; + template<> + struct StringMaker { + static std::string convert(double value); + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template + struct is_range { + static const bool value = + !std::is_same())), not_this_one>::value && + !std::is_same())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include +#include +#include + +namespace Catch { + +template +struct ratio_string { + static std::string symbol(); +}; + +template +std::string ratio_string::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } + template + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + template + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + + template + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } + template + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + template + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template + auto operator == ( RhsT const& rhs ) -> BinaryExpr const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template + auto operator != ( RhsT const& rhs ) -> BinaryExpr const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template + auto operator > ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; + } + template + auto operator < ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; + } + template + auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template + auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template + void handleExpression( ExprLhs const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template + auto operator <= ( T const& lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + + struct ITransientExpression; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, false && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include +#include + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include +#include +#include + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template ::value>::type> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast(newEpsilon); + if( epsilonAsDouble < 0 || epsilonAsDouble > 1.0 ) { + throw std::domain_error + ( "Invalid Approx::epsilon: " + + Catch::Detail::stringify( epsilonAsDouble ) + + ", Approx::epsilon has to be between 0 and 1" ); + } + m_epsilon = epsilonAsDouble; + return *this; + } + + template ::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast(newMargin); + if( marginAsDouble < 0 ) { + throw std::domain_error + ( "Invalid Approx::margin: " + + Catch::Detail::stringify( marginAsDouble ) + + ", Approx::Margin has to be non-negative." ); + + } + m_margin = marginAsDouble; + return *this; + } + + template ::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include +#include + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template +class PredicateMatcher : public MatcherBase { + std::function m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function is hard (but possible) and + // requires a lot of TMP. + template + Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template + struct ContainsElementMatcher : MatcherBase> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase> { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase> { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector const& m_comparator; + }; + + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst != *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); + + template + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr { + return MatchExpr( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector tags; + std::vector lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared( token ); + if( m_exclusion ) + pattern = std::make_shared( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr; +} + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector const& getTestsOrTags() const; + std::vector const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include +#include +#include +#include +#include + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template + struct LazyStat : Option { + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr; + + struct IReporterRegistry { + using FactoryMap = std::map; + using Listeners = std::vector; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + template + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + struct CumulativeReporterBase : IStreamingReporter { + template + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector>; + using Assertions = std::vector; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node; + using TestGroupNode = Node; + using TestRunNode = Node; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector>> m_sections; + std::vector> m_testCases; + std::vector> m_testGroups; + + std::vector> m_testRuns; + + std::shared_ptr m_rootSection; + std::shared_ptr m_deepestSection; + std::vector> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase { + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include +#include +#include + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ); + bool operator ()( ITrackerPtr const& tracker ) const; + }; + + using Children = std::vector; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector const& filters ); + void addNextFilters( std::vector const& filters ); + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); + + int index() const; + + void moveNext(); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include +#include + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if( m_reaction.shouldThrow ) + throw Catch::TestFailureException(); + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.4 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include +#include +#include +#include + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { namespace clara { namespace TextFlow { + + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } + + class Columns; + + class Column { + std::vector m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); + + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); + } + + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); + + m_suffix = false; + auto width = m_column.m_width-indent(); + m_end = m_pos; + while( m_end < line().size() && line()[m_end] != '\n' ) + ++m_end; + + if( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos <= m_end ); + if( m_pos + m_column.m_width < m_end ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if( m_pos < line().size() && line()[m_pos] == '\n' ) + m_pos += 1; + else + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; + + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ) { m_strings.push_back( text ); } + + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + ( Column const& other ) -> Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + class Spacer : public Column { + + public: + explicit Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } + }; + + class Columns { + std::vector m_columns; + + public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } + + public: + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); + } + + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } +}}} // namespace Catch::clara::TextFlow + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include +#include +#include + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const &other ) + : ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + struct LambdaInvoker { + static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda::ArgType>( m_lambda, arg ); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + + public: + template + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint( hint ) + {} + + template + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared( "" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + + template + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator|( T const &other ) const -> Parser { + return Parser() | static_cast( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include +#include + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( config.reporterName, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include +#include + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +// start catch_enforce.h + +#include + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); +#define CATCH_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include +# include +# include +# include +# include +# include +# include + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_errno_guard.cpp + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include +#include +#include + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr( translator ) ); + } + + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = ""; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters( Config const& /*config*/ ); + + Option list( Config const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include +#include +#include + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_to_string.hpp + +#include + +namespace Catch { + template + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include +#include +#include +#include + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template +struct Converter; + +template <> +struct Converter { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter { + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (std::isnan(lhs) || std::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + if (m_margin < 0) { + throw std::domain_error("Allowed margin difference has to be >= 0"); + } + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + if (m_ulps < 0) { + throw std::domain_error("Allowed ulp difference has to be >= 0"); + } + } + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + throw std::domain_error("Unknown FloatingPointKind value"); + } + } + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + throw std::runtime_error("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + throw std::runtime_error("Could not translate errno to string"); + } + throw std::runtime_error("Could not open the temp file: " + std::string(m_buffer) + buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + throw std::runtime_error("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_random_number_generator.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include +#include +#include +#include + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector const& functions ); + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector const& getAllTests() const override; + std::vector const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector const& getExceptions() const noexcept; + private: + std::vector m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + + // Single, global, instance + RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = nullptr; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = nullptr; + cleanUpContext(); + ReusableStringStream::cleanup(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include +#include +#include + +namespace Catch { + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed == static_cast(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + try { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } catch (TestFailureException&) { + // This just means the test was aborted due to failure + } catch (...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + + void useConfigData( ConfigData const& configData ); + + int run( int argc, char* argv[] ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t* const argv[] ); + #endif + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include +#include + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr(new ListeningReporter); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); + } + + Catch::Totals runTests(std::shared_ptr const& config) { + // FixMe: Add listeners in order first, then add reporters. + + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + catch(...) { getMutableRegistryHub().registerStartupException(); } + } + + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run( int argc, char* argv[] ) { + if( m_startupExceptions ) + return 1; + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::run( int argc, wchar_t* const argv[] ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = run( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if( m_configData.showHelp || m_configData.libIdentify ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + try { + m_exceptions.push_back(exception); + } + catch(...) { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + static StringStreams* s_instance; + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + + // !TBD: put in TLS + static auto instance() -> StringStreams& { + if( !s_instance ) + s_instance = new StringStreams(); + return *s_instance; + } + static void cleanup() { + delete s_instance; + s_instance = nullptr; + } + }; + + StringStreams* StringStreams::s_instance = nullptr; + + void ReusableStringStream::cleanup() { + StringStreams::cleanup(); + } + + ReusableStringStream::ReusableStringStream() + : m_index( StringStreams::instance().add() ), + m_oss( StringStreams::instance().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + StringStreams::instance().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + try { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include + +namespace Catch { + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { + return + tracker->nameAndLocation().location == m_nameAndLocation.location && + tracker->nameAndLocation().name == m_nameAndLocation.name; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast( childTracker ); + } + else { + section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + try { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include +#include + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } +} + +template +std::string fpToString( T value, int precision ) { + if (std::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} +#endif + +std::string StringMaker::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker::convert(char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(signed char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} + +std::string StringMaker::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 3, 0, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + if (result.isOk()) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set ListeningReporter::getSupportedVerbosities() { + return std::set{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/gizmo/test/core.cpp b/gizmo/test/core.cpp new file mode 100644 index 0000000..3735974 --- /dev/null +++ b/gizmo/test/core.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + diff --git a/gizmo/test/test.mk b/gizmo/test/test.mk new file mode 100644 index 0000000..578453a --- /dev/null +++ b/gizmo/test/test.mk @@ -0,0 +1,53 @@ +#################################### +### default global configuration ### +#################################### + +TEST_DIR = test + +TEST_TARGET = $(TEST_DIR)/tests + + +###################### +### artifact files ### +###################### + +TEST_SOURCES = \ + $(TEST_DIR)/core.cpp \ + $(TEST_DIR)/utf8.cpp \ + +TEST_OBJECTS = $(TEST_SOURCES:.cpp=.o) + +TEST_DEPENDS = $(TEST_SOURCES:.cpp=.d) + +TESTEE_OBJECTS = \ + text/utf8.o + + +##################### +### build recipes ### +##################### + +test: CXXFLAGS += $(CXXFLAGS_DBG) +test: LDFLAGS += $(LDFLAGS_DBG) +test: $(TEST_TARGET) | .FORCE + $(TEST_TARGET) + +test-clean: + $(RM) $(TEST_OBJECTS) + $(RM) $(TEST_TARGET) + +clean: test-clean + + +-include $(DEPENDS) + +$(TEST_OBJECTS): %.o: %.cpp + $(CXX) -c -o $@ $< $(CXXFLAGS) + $(CXX) -MM $(CXXFLAGS) $*.cpp -MF $*.d -MQ $@ + + +$(TEST_TARGET): $(TEST_OBJECTS) $(TESTEE_OBJECTS) + $(CXX) -o $@ $^ $(LDFLAGS) + +.PHONY: test test-clean +.PHONY: .FORCE diff --git a/gizmo/test/utf8.cpp b/gizmo/test/utf8.cpp new file mode 100644 index 0000000..6062180 --- /dev/null +++ b/gizmo/test/utf8.cpp @@ -0,0 +1,203 @@ +#include "catch.hpp" +#include "text/utf8.h" +#include +#include + +using namespace std; + + + +basic_string mkbstr1(uint32_t cp) +{ + basic_string res; + res.push_back(cp); + return res; +} + +basic_string mkbstr(size_t len, ...) +{ + basic_string res; + res.reserve(len); + va_list vl; + va_start(vl, len); + for (size_t i = 0; i < len; i++) + res.push_back(va_arg(vl, uint32_t)); + va_end(vl); + return res; +} + + +TEST_CASE("UTF-8") +{ + SECTION("decode valid codepoints") + { + REQUIRE( Utf8::decode("a") == mkbstr1('a') ); + REQUIRE( Utf8::decode("\xc3\xb1") == mkbstr1(0xf1) ); + REQUIRE( Utf8::decode("\xe2\x82\xa1") == mkbstr1(0x20a1) ); + REQUIRE( Utf8::decode("\xf0\x90\x8c\xbc") == mkbstr1(0x1033c) ); + + REQUIRE( Utf8::decode("\xc2\x80") == mkbstr1(0x80) ); + REQUIRE( Utf8::decode("\xe0\xa0\x80") == mkbstr1(0x800) ); + REQUIRE( Utf8::decode("\xf0\x90\x80\x80") == mkbstr1(0x10000) ); + + REQUIRE( Utf8::decode("\x7f") == mkbstr1(0x7f) ); + REQUIRE( Utf8::decode("\xdf\xbf") == mkbstr1(0x7ff) ); + REQUIRE( Utf8::decode("\xef\xbf\xbf") == mkbstr1(0xffff) ); + + REQUIRE( Utf8::decode("\xee\x80\x80") == mkbstr1(0xe000) ); + REQUIRE( Utf8::decode("\xef\xbf\xbd") == mkbstr1(0xfffd) ); + REQUIRE( Utf8::decode("\xf4\x90\x80\x80") == mkbstr1(0x110000) ); + } + + SECTION("encode valid codepoints") + { + REQUIRE( Utf8::encode('a') == "a" ); + REQUIRE( Utf8::encode(0xf1) == "\xc3\xb1" ); + REQUIRE( Utf8::encode(0x20a1) == "\xe2\x82\xa1" ); + REQUIRE( Utf8::encode(0x1033c) == "\xf0\x90\x8c\xbc" ); + + REQUIRE( Utf8::encode(0x80) == "\xc2\x80" ); + REQUIRE( Utf8::encode(0x800) == "\xe0\xa0\x80" ); + REQUIRE( Utf8::encode(0x10000) == "\xf0\x90\x80\x80" ); + + REQUIRE( Utf8::encode(0x7f) == "\x7f" ); + REQUIRE( Utf8::encode(0x7ff) == "\xdf\xbf" ); + REQUIRE( Utf8::encode(0xffff) == "\xef\xbf\xbf" ); + + REQUIRE( Utf8::encode(0xe000) == "\xee\x80\x80" ); + REQUIRE( Utf8::encode(0xfffd) == "\xef\xbf\xbd" ); + REQUIRE( Utf8::encode(0x110000) == "\xf4\x90\x80\x80" ); + } + + SECTION("validation") + { + SECTION("validate legal sequence") + { + REQUIRE( true == Utf8::validate("a") ); + REQUIRE( true == Utf8::validate("\xc3\xb1") ); + REQUIRE( true == Utf8::validate("\xe2\x82\xa1") ); + REQUIRE( true == Utf8::validate("\xf0\x90\x8c\xbc") ); + + REQUIRE( true == Utf8::validate("\xc2\x80") ); + REQUIRE( true == Utf8::validate("\xe0\xa0\x80") ); + REQUIRE( true == Utf8::validate("\xf0\x90\x80\x80") ); + + REQUIRE( true == Utf8::validate("\x7f") ); + REQUIRE( true == Utf8::validate("\xdf\xbf") ); + REQUIRE( true == Utf8::validate("\xef\xbf\xbf") ); + + REQUIRE( true == Utf8::validate("\xee\x80\x80") ); + REQUIRE( true == Utf8::validate("\xef\xbf\xbd") ); + REQUIRE( true == Utf8::validate("\xf4\x90\x80\x80") ); + } + + SECTION("validate illegal sequence") + { + REQUIRE( false == Utf8::validate("\xc3\x28") ); + REQUIRE( false == Utf8::validate("\xa0\xa1") ); + REQUIRE( false == Utf8::validate("\xe2\x28\xa1") ); + REQUIRE( false == Utf8::validate("\xe2\x82\x28") ); + REQUIRE( false == Utf8::validate("\xf0\x28\x8c\xbc") ); + REQUIRE( false == Utf8::validate("\xf0\x90\x28\xbc") ); + REQUIRE( false == Utf8::validate("\xf0\x28\x8c\x28") ); + REQUIRE( false == Utf8::validate("\xfe") ); + REQUIRE( false == Utf8::validate("\xff") ); + REQUIRE( false == Utf8::validate("\xfe\xfe\xff\xff") ); + REQUIRE( false == Utf8::validate("\xc0") ); + REQUIRE( false == Utf8::validate("\xc1") ); + } + + SECTION("validate too long sequences") + { + REQUIRE( false == Utf8::validate("\xf8\xa1\xa1\xa1\xa1") ); + REQUIRE( false == Utf8::validate("\xfc\xa1\xa1\xa1\xa1\xa1") ); + } + + SECTION("validate over-long sequences") + { + REQUIRE( false == Utf8::validate("\xc0\xaf") ); + REQUIRE( false == Utf8::validate("\xe0\x80\xaf") ); + REQUIRE( false == Utf8::validate("\xf0\x80\x80\xaf") ); + REQUIRE( false == Utf8::validate("\xf8\x80\x80\x80\xaf") ); + REQUIRE( false == Utf8::validate("\xfc\x80\x80\x80\x80\xaf") ); + + REQUIRE( false == Utf8::validate("\xc1\xbf") ); + REQUIRE( false == Utf8::validate("\xe0\x9f\xbf") ); + REQUIRE( false == Utf8::validate("\xf0\x8f\xbf\xbf") ); + REQUIRE( false == Utf8::validate("\xf8\x87\xbf\xbf\xbf") ); + REQUIRE( false == Utf8::validate("\xfc\x83\xbf\xbf\xbf\xbf") ); + + REQUIRE( false == Utf8::validate("\xc0\x80") ); + REQUIRE( false == Utf8::validate("\xe0\x80\x80") ); + REQUIRE( false == Utf8::validate("\xf0\x80\x80\x80") ); + REQUIRE( false == Utf8::validate("\xf8\x80\x80\x80\x80") ); + REQUIRE( false == Utf8::validate("\xfc\x80\x80\x80\x80\x80") ); + } + } + + SECTION("escape") + { + SECTION("escape valid sequences") + { + REQUIRE( Utf8::escape("a") == "a" ); + REQUIRE( Utf8::escape("\xc3\xb1") == "\xc3\xb1" ); + REQUIRE( Utf8::escape("\xe2\x82\xa1") == "\xe2\x82\xa1" ); + REQUIRE( Utf8::escape("\xf0\x90\x8c\xbc") == "\xf0\x90\x8c\xbc" ); + + REQUIRE( Utf8::escape("\xc2\x80") == "\xc2\x80" ); + REQUIRE( Utf8::escape("\xe0\xa0\x80") == "\xe0\xa0\x80" ); + REQUIRE( Utf8::escape("\xf0\x90\x80\x80") == "\xf0\x90\x80\x80" ); + + REQUIRE( Utf8::escape("\x7f") == "\x7f" ); + REQUIRE( Utf8::escape("\xdf\xbf") == "\xdf\xbf" ); + REQUIRE( Utf8::escape("\xef\xbf\xbf") == "\xef\xbf\xbf" ); + + REQUIRE( Utf8::escape("\xee\x80\x80") == "\xee\x80\x80" ); + REQUIRE( Utf8::escape("\xef\xbf\xbd") == "\xef\xbf\xbd" ); + REQUIRE( Utf8::escape("\xf4\x90\x80\x80") == "\xf4\x90\x80\x80" ); + } + + SECTION("escape invalid sequences") + { + REQUIRE( Utf8::validate( Utf8::escape("\xc3\x28") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xa0\xa1") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xe2\x28\xa1") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xe2\x82\x28") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x28\x8c\xbc") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x90\x28\xbc") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x28\x8c\x28") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfe") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xff") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfe\xfe\xff\xff") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xc0") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xc1") ) ); + } + + SECTION("escape too long sequences") + { + REQUIRE( Utf8::validate( Utf8::escape("\xf8\xa1\xa1\xa1\xa1") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfc\xa1\xa1\xa1\xa1\xa1") ) ); + } + + SECTION("escape over-long sequences") + { + REQUIRE( Utf8::validate( Utf8::escape("\xc0\xaf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xe0\x80\xaf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x80\x80\xaf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf8\x80\x80\x80\xaf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfc\x80\x80\x80\x80\xaf") ) ); + + REQUIRE( Utf8::validate( Utf8::escape("\xc1\xbf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xe0\x9f\xbf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x8f\xbf\xbf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf8\x87\xbf\xbf\xbf") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfc\x83\xbf\xbf\xbf\xbf") ) ); + + REQUIRE( Utf8::validate( Utf8::escape("\xc0\x80") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xe0\x80\x80") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf0\x80\x80\x80") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xf8\x80\x80\x80\x80") ) ); + REQUIRE( Utf8::validate( Utf8::escape("\xfc\x80\x80\x80\x80\x80") ) ); + } + } +} diff --git a/gizmo/text/dictionary.cpp b/gizmo/text/dictionary.cpp new file mode 100644 index 0000000..b387434 --- /dev/null +++ b/gizmo/text/dictionary.cpp @@ -0,0 +1,65 @@ +#include "dictionary.h" +#include "utf8.h" + +using namespace std; + + +void Dictionary::add(const string &key, const string &value) +{ + auto &values = m_entrys[key]; + for (auto &v : values) + { + if (value == v) + return; + } + values.push_back(value); +} + +size_t Dictionary::size() const +{ + return m_entrys.size(); +} + +const vector &Dictionary::translate(const string &text) const +{ + Entrys::const_iterator entry = m_entrys.find(text); + if (entry == m_entrys.end()) + { + static const vector empty; + return empty; + } + + return entry->second; +} + +Dictionary::Entrys::const_iterator Dictionary::bestGuess(const string &text) const +{ + Entrys::const_iterator it = m_entrys.lower_bound(text); + if (it == m_entrys.end()) + it = m_entrys.upper_bound(text); + + return it; +} + +Dictionary::Entrys::const_iterator Dictionary::end() const +{ + return m_entrys.end(); +} + + +float compareWords(const string &word1, const string &word2) +{ + Utf8::iterator w1(word1); + Utf8::iterator w2(word2); + float sim = 0.f; + + while (*w1 && *w1 == *w2) + { + sim += 2.f; + ++w1; + ++w2; + } + + return sim / (sim + (float)(w1.size() + w2.size())); +} + diff --git a/gizmo/text/dictionary.h b/gizmo/text/dictionary.h new file mode 100644 index 0000000..490e35b --- /dev/null +++ b/gizmo/text/dictionary.h @@ -0,0 +1,31 @@ +#ifndef __DICTIONARY_H__ +#define __DICTIONARY_H__ + +#include +#include +#include +#include + + +class Dictionary +{ + public: + typedef std::map< std::string, std::vector > Entrys; + + public: + void add(const std::string &key, const std::string &value); + size_t size() const; + + const std::vector &translate(const std::string &text) const; + + Entrys::const_iterator bestGuess(const std::string &text) const; + Entrys::const_iterator end() const; + + private: + Entrys m_entrys; +}; + + +float compareWords(const std::string &word1, const std::string &word2); + +#endif diff --git a/gizmo/text/translator.cpp b/gizmo/text/translator.cpp new file mode 100644 index 0000000..9ff2a1b --- /dev/null +++ b/gizmo/text/translator.cpp @@ -0,0 +1,49 @@ +#include "translator.h" +#include "utf8.h" + +using namespace std; + + +Translator::Translator(const Dictionary &dict) : m_dict(dict), m_minSim(1.0f) +{ +} + +void Translator::connectWordsCallback(WordsCallback callback) +{ + m_wordsCb = callback; +} + +void Translator::setMinWordsSim(float minSim) +{ + m_minSim = minSim; +} + +void Translator::pushWord(const string &word, double time) +{ + const string lword = Utf8::toLower(word); + auto it1 = m_dict.bestGuess(lword); + auto it2 = it1; + + if (it1 == m_dict.end()) + return; + + for (; it1 != m_dict.end(); --it1) + { + float sim = compareWords(lword, it1->first); + if (sim < m_minSim) + break; + + for (auto &tr : it1->second) + m_wordsCb(tr, time); + } + + for (++it2; it2 != m_dict.end(); ++it2) + { + float sim = compareWords(lword, it2->first); + if (sim < m_minSim) + break; + + for (auto &tr : it2->second) + m_wordsCb(tr, time); + } +} diff --git a/gizmo/text/translator.h b/gizmo/text/translator.h new file mode 100644 index 0000000..dfe1d80 --- /dev/null +++ b/gizmo/text/translator.h @@ -0,0 +1,25 @@ +#ifndef __TRANSLATOR_H__ +#define __TRANSLATOR_H__ + +#include "text/words.h" +#include "text/dictionary.h" +#include + + +class Translator +{ + public: + Translator(const Dictionary &dictionary); + + void pushWord(const std::string &word, double time); + + void connectWordsCallback(WordsCallback m_wordsCb); + void setMinWordsSim(float minSim); + + private: + const Dictionary &m_dict; + WordsCallback m_wordsCb; + float m_minSim; +}; + +#endif diff --git a/gizmo/text/utf8.cpp b/gizmo/text/utf8.cpp new file mode 100644 index 0000000..dda3ade --- /dev/null +++ b/gizmo/text/utf8.cpp @@ -0,0 +1,318 @@ +#include "utf8.h" +#include "utf8case.h" + +#define IS1B(cp) (((cp) & 0x80) == 0x00) +#define IS2B(cp) (((cp) & 0xe0) == 0xc0) +#define IS3B(cp) (((cp) & 0xf0) == 0xe0) +#define IS4B(cp) (((cp) & 0xf8) == 0xf0) +#define ISCNT(cp) (((cp) & 0xc0) == 0x80) + +#define CNT(b, pos) (((b) & 0x3f) << ((pos) * 6)) + +#define ENC_CNT(x, no) (((x>>(no*6)) & 0x3f) | 0x80) + +#define DECODE2B(p) ((((p)[0] & 0x1f) << 6) | CNT((p)[1], 0)) +#define DECODE3B(p) ((((p)[0] & 0x0f) << 12) | CNT((p)[1], 1) | CNT((p)[2], 0)) +#define DECODE4B(p) ((((p)[0] & 0x07) << 18) | CNT((p)[1], 2) | CNT((p)[2], 1) | CNT((p)[3], 0)) + +using namespace std; + + +const uint32_t Utf8::iterator::invalid = 0xffffffff; + +static uint32_t lowerToUpper(uint32_t lower, uint32_t defval=Utf8::iterator::invalid); +static uint32_t upperToLower(uint32_t upper, uint32_t defval=Utf8::iterator::invalid); + + +Utf8::iterator::iterator() : m_ptr(NULL) +{} + +Utf8::iterator::iterator(const string &str) : m_ptr((const uint8_t*)str.c_str()) +{} + +Utf8::iterator::iterator(const char *str) : m_ptr((const uint8_t*)str) +{} + +uint32_t Utf8::iterator::operator* () const +{ + if (m_ptr == NULL) + return 0; + if (IS1B(m_ptr[0])) + return m_ptr[0]; + if (IS2B(m_ptr[0]) && ISCNT(m_ptr[1])) + return DECODE2B(m_ptr); + if (IS3B(m_ptr[0]) && ISCNT(m_ptr[1]) && ISCNT(m_ptr[2])) + return DECODE3B(m_ptr); + if (IS4B(m_ptr[0]) && ISCNT(m_ptr[1]) && ISCNT(m_ptr[2]) && ISCNT(m_ptr[3])) + return DECODE4B(m_ptr); + + return invalid; +} + +Utf8::iterator &Utf8::iterator::operator++ () +{ + if (m_ptr) + m_ptr++; + + while (m_ptr && ISCNT(*m_ptr)) + m_ptr++; + + return *this; +} + +Utf8::iterator &Utf8::iterator::operator-- () +{ + if (m_ptr) + m_ptr--; + + while (m_ptr && ISCNT(*m_ptr)) + m_ptr--; + + return *this; +} + +Utf8::iterator Utf8::iterator::operator++ (int) +{ + iterator cp = *this; + operator++(); + return cp; +} + +Utf8::iterator Utf8::iterator::operator-- (int) +{ + iterator cp = *this; + operator--(); + return cp; +} + +size_t Utf8::iterator::size() const +{ + if (m_ptr == NULL) + return 0; + + size_t s = 0; + const uint8_t *p = m_ptr; + + while (*p) + { + if (!ISCNT(*p)) s++; + p++; + } + + return s; +} + +uint32_t Utf8::iterator::toLower() const +{ + uint32_t cp = operator*(); + return upperToLower(cp, cp); +} + +uint32_t Utf8::iterator::toUpper() const +{ + uint32_t cp = operator*(); + return lowerToUpper(cp, cp); +} + +bool Utf8::iterator::isLower() const +{ + return lowerToUpper(operator*()) != Utf8::iterator::invalid; +} + +bool Utf8::iterator::isUpper() const +{ + return upperToLower(operator*()) != Utf8::iterator::invalid; +} + +const char *Utf8::iterator::getRawData() const +{ + return (const char*)m_ptr; +} + +unsigned Utf8::iterator::getiteratorSize() const +{ + if (m_ptr) + { + if (IS1B(*m_ptr)) return 1; + if (IS2B(*m_ptr)) return 2; + if (IS3B(*m_ptr)) return 3; + if (IS4B(*m_ptr)) return 4; + } + return 0; +} + +basic_string Utf8::decode(const string &str) +{ + basic_string res; + for (Utf8::iterator it(str); *it; ++it) + res += *it; + + return res; +} + +string Utf8::encode(uint32_t codePoint) +{ + string res; + if (codePoint <= 0x7f) + { + res.reserve(1); + res = (char)codePoint; + } + else if (codePoint <= 0x7ff) + { + res.reserve(2); + res = (char)((codePoint >> 6) | 0xc0); + res += (char)ENC_CNT(codePoint, 0); + } + else if (codePoint <= 0xffff) + { + res.reserve(3); + res = (char)((codePoint >> 12) | 0xe0); + res += (char)ENC_CNT(codePoint, 1); + res += (char)ENC_CNT(codePoint, 0); + } + else if (codePoint <= 0x1fffff) + { + res.reserve(4); + res = (char)((codePoint >> 18) | 0xf0); + res += (char)ENC_CNT(codePoint, 2); + res += (char)ENC_CNT(codePoint, 1); + res += (char)ENC_CNT(codePoint, 0); + } + + return res; +} + +string Utf8::encode(const basic_string &codePoints) +{ + string res; + for (uint32_t codePoint : codePoints) + res += encode(codePoint); + + return res; +} + +string Utf8::toLower(const string &str) +{ + string res; + for (Utf8::iterator it(str); *it; ++it) + res += Utf8::encode(it.toLower()); + + return res; +} + +string toUpper(const string &str) +{ + string res; + for (Utf8::iterator it(str); *it; ++it) + res += Utf8::encode(it.toUpper()); + + return res; +} + +uint32_t lowerToUpper(uint32_t lower, uint32_t defval) +{ + for (unsigned i = 0; i < sizeof(g_caseGroup1) / sizeof(Utf8CaseGroup); i++) + { + const Utf8CaseGroup &gr = g_caseGroup1[i]; + if (lower >= gr.lower && lower < gr.lower + gr.count) + return gr.upper + (lower - gr.lower); + + if (lower > gr.lower) + break; + } + + for (unsigned i = 0; i < sizeof(g_caseGroup2) / sizeof(Utf8CaseGroup); i++) + { + const Utf8CaseGroup &gr = g_caseGroup2[i]; + if ((lower & 1) == (gr.lower & 1) && lower >= gr.lower && lower < gr.lower + 2*gr.count) + return gr.upper + (lower - gr.lower); + + if (lower > gr.lower) + break; + } + + for (unsigned i = 0; i < sizeof(g_caseSingle) / sizeof(Utf8CaseMapping); i++) + { + const Utf8CaseMapping &cm = g_caseSingle[i]; + if (cm.lower == lower) + return cm.upper; + + if (lower > cm.lower) + break; + } + + return defval; +} + +uint32_t upperToLower(uint32_t upper, uint32_t defval) +{ + for (unsigned i = 0; i < sizeof(g_caseGroup1) / sizeof(Utf8CaseGroup); i++) + { + const Utf8CaseGroup &gr = g_caseGroup1[i]; + if (upper >= gr.upper && upper < gr.upper + gr.count) + return gr.lower + (upper - gr.upper); + } + + for (unsigned i = 0; i < sizeof(g_caseGroup2) / sizeof(Utf8CaseGroup); i++) + { + const Utf8CaseGroup &gr = g_caseGroup2[i]; + if ((upper & 1) == (gr.upper & 1) && upper >= gr.upper && upper < gr.upper + 2*gr.count) + return gr.lower + (upper - gr.upper); + } + + for (unsigned i = 0; i < sizeof(g_caseSingle) / sizeof(Utf8CaseMapping); i++) + { + const Utf8CaseMapping &cm = g_caseSingle[i]; + if (cm.upper == upper) + return cm.lower; + } + + return defval; +} + +bool Utf8::validate(const string &str) +{ + return Utf8::validate(str.c_str()); +} + +bool Utf8::validate(const char *str) +{ + const char *p = str; + while (*p) + { + if (IS1B(p[0])) + p += 1; + else if (IS2B(p[0]) && ISCNT(p[1]) && (DECODE2B(p) >= 0x80)) + p += 2; + else if (IS3B(p[0]) && ISCNT(p[1]) && ISCNT(p[2]) && (DECODE3B(p) >= 0x800)) + p += 3; + else if (IS4B(p[0]) && ISCNT(p[1]) && ISCNT(p[2]) && ISCNT(p[3]) && (DECODE4B(p) >= 0x10000)) + p += 4; + else + return false; + } + + return true; +} + +string Utf8::escape(const std::string &str) +{ + return Utf8::escape(str.c_str()); +} + +string Utf8::escape(const char *str) +{ + string res; + for (Utf8::iterator it(str); *it; ++it) + { + uint32_t cp = *it; + if (cp == Utf8::iterator::invalid) + cp = 0xfffd; + + res += Utf8::encode(cp); + } + + return res; +} + diff --git a/gizmo/text/utf8.h b/gizmo/text/utf8.h new file mode 100644 index 0000000..4f6ebe7 --- /dev/null +++ b/gizmo/text/utf8.h @@ -0,0 +1,60 @@ +#ifndef __UTF8_H__ +#define __UTF8_H__ + +#include + + +class Utf8 +{ + public: + class iterator + { + public: + iterator(); + iterator(const std::string &str); + iterator(const char *str); + + uint32_t operator* () const; + + iterator &operator++ (); + iterator &operator-- (); + + iterator operator++ (int); + iterator operator-- (int); + + bool operator== (const iterator &it) const; + bool operator!= (const iterator &it) const; + + size_t size() const; + + uint32_t toLower() const; + uint32_t toUpper() const; + + bool isLower() const; + bool isUpper() const; + + const char *getRawData() const; + unsigned getiteratorSize() const; + + static const uint32_t invalid; + + private: + const uint8_t *m_ptr; + }; + + public: + static std::basic_string decode(const std::string &str); + static std::string encode(uint32_t codePoint); + static std::string encode(const std::basic_string &codePoints); + + static std::string toLower(const std::string &str); + static std::string toUpper(const std::string &str); + + static bool validate(const std::string &str); + static bool validate(const char *str); + + static std::string escape(const std::string &str); + static std::string escape(const char *str); +}; + +#endif diff --git a/gizmo/text/utf8case.h b/gizmo/text/utf8case.h new file mode 100644 index 0000000..8136f82 --- /dev/null +++ b/gizmo/text/utf8case.h @@ -0,0 +1,217 @@ +#include + +struct Utf8CaseGroup +{ + uint32_t lower; + uint32_t upper; + unsigned count; +}; + + +struct Utf8CaseMapping +{ + uint32_t lower; + uint32_t upper; +}; + + +static const Utf8CaseGroup g_caseGroup1[] = { + {0x0061, 0x0041, 26}, + {0x00e0, 0x00c0, 23}, + {0x00f8, 0x00d8, 7}, + {0x023f, 0x2c7e, 2}, + {0x0258, 0x018e, 2}, + {0x028a, 0x01b1, 2}, + {0x037b, 0x03fd, 3}, + {0x03ad, 0x0388, 2}, + {0x03ae, 0x0389, 2}, + {0x03b1, 0x0391, 17}, + {0x03c3, 0x03a3, 9}, + {0x03cd, 0x038e, 2}, + {0x0430, 0x0410, 32}, + {0x0450, 0x0400, 16}, + {0x0561, 0x0531, 38}, + {0x1d48, 0x1d30, 2}, + {0x1d57, 0x1d40, 2}, + {0x1f00, 0x1f08, 8}, + {0x1f10, 0x1f18, 6}, + {0x1f20, 0x1f28, 8}, + {0x1f30, 0x1f38, 8}, + {0x1f40, 0x1f48, 6}, + {0x1f60, 0x1f68, 8}, + {0x1fb0, 0x1fb8, 2}, + {0x1fd0, 0x1fd8, 2}, + {0x1fe0, 0x1fe8, 2}, + {0x249c, 0x1f110, 26}, + {0x24d0, 0x24b6, 26}, + {0x2c30, 0x2c00, 47}, + {0x2d00, 0x10a0, 38}, + {0xff41, 0xff21, 26}, + {0x10428, 0x10400, 40}, + {0x1d41a, 0x1d400, 26}, + {0x1d44e, 0x1d434, 7}, + {0x1d456, 0x1d43c, 18}, + {0x1d482, 0x1d468, 26}, + {0x1d4b8, 0x1d49e, 2}, + {0x1d4bf, 0x1d4a5, 2}, + {0x1d4c5, 0x1d4ab, 2}, + {0x1d4c8, 0x1d4ae, 8}, + {0x1d4ea, 0x1d4d0, 26}, + {0x1d51e, 0x1d504, 2}, + {0x1d521, 0x1d507, 4}, + {0x1d527, 0x1d50d, 8}, + {0x1d530, 0x1d516, 7}, + {0x1d552, 0x1d538, 2}, + {0x1d555, 0x1d53b, 4}, + {0x1d55a, 0x1d540, 5}, + {0x1d564, 0x1d54a, 7}, + {0x1d586, 0x1d56c, 26}, + {0x1d5ba, 0x1d5a0, 26}, + {0x1d5ee, 0x1d5d4, 26}, + {0x1d622, 0x1d608, 26}, + {0x1d656, 0x1d63c, 26}, + {0x1d68a, 0x1d670, 26}, + {0x1d6c2, 0x1d6a8, 17}, + {0x1d6d4, 0x1d6ba, 7}, + {0x1d6fc, 0x1d6e2, 17}, + {0x1d70e, 0x1d6f4, 7}, + {0x1d736, 0x1d71c, 17}, + {0x1d748, 0x1d72e, 7}, + {0x1d770, 0x1d756, 17}, + {0x1d782, 0x1d768, 7}, + {0x1d7aa, 0x1d790, 17}, + {0x1d7bc, 0x1d7a2, 7}, + {0xe0061, 0xe0041, 26}, +}; + +static const Utf8CaseGroup g_caseGroup2[] = { + {0x0101, 0x0100, 24}, + {0x0133, 0x0132, 3}, + {0x013a, 0x0139, 8}, + {0x014b, 0x014a, 23}, + {0x017a, 0x0179, 3}, + {0x0183, 0x0182, 2}, + {0x01a1, 0x01a0, 3}, + {0x01b4, 0x01b3, 2}, + {0x01ce, 0x01cd, 8}, + {0x01df, 0x01de, 9}, + {0x01f9, 0x01f8, 20}, + {0x0223, 0x0222, 9}, + {0x0247, 0x0246, 2}, + {0x024d, 0x024c, 2}, + {0x02b0, 0x1d34, 2}, + {0x0371, 0x0370, 2}, + {0x03ad, 0x0388, 2}, + {0x03e3, 0x03e2, 7}, + {0x0461, 0x0460, 17}, + {0x048b, 0x048a, 27}, + {0x04c2, 0x04c1, 7}, + {0x04d1, 0x04d0, 44}, + {0x1e01, 0x1e00, 75}, + {0x1ea1, 0x1ea0, 48}, + {0x1f51, 0x1f59, 4}, + {0x1f72, 0x1fc8, 2}, + {0x2c68, 0x2c67, 3}, + {0x2c81, 0x2c80, 50}, + {0x2cec, 0x2ceb, 2}, + {0xa641, 0xa640, 23}, + {0xa681, 0xa680, 12}, + {0xa723, 0xa722, 7}, + {0xa733, 0xa732, 31}, + {0xa77a, 0xa779, 2}, + {0xa77f, 0xa77e, 5}, + {0xa7a1, 0xa7a0, 5}, +}; + +static const Utf8CaseMapping g_caseSingle[] = { + {0x00df, 0x1e9e}, + {0x00ff, 0x0178}, + {0x0180, 0x0243}, + {0x0188, 0x0187}, + {0x018c, 0x018b}, + {0x0192, 0x0191}, + {0x0199, 0x0198}, + {0x019a, 0x023d}, + {0x019e, 0x0220}, + {0x01a8, 0x01a7}, + {0x01ad, 0x01ac}, + {0x01b0, 0x01af}, + {0x01b9, 0x01b8}, + {0x01bd, 0x01bc}, + {0x01c6, 0x01c4}, + {0x01c9, 0x01c7}, + {0x01cc, 0x01ca}, + {0x01f3, 0x01f1}, + {0x01f5, 0x01f4}, + {0x023c, 0x023b}, + {0x0242, 0x0241}, + {0x0250, 0x2c6f}, + {0x0251, 0x2c6d}, + {0x0252, 0x2c70}, + {0x0253, 0x0181}, + {0x0254, 0x0186}, + {0x0257, 0x018a}, + {0x025b, 0x0190}, + {0x0260, 0x0193}, + {0x0263, 0x0194}, + {0x0265, 0xa78d}, + {0x0268, 0x0197}, + {0x0269, 0x0196}, + {0x026b, 0x2c62}, + {0x026f, 0x019c}, + {0x0271, 0x2c6e}, + {0x0272, 0x019d}, + {0x027d, 0x2c64}, + {0x0283, 0x01a9}, + {0x0288, 0x01ae}, + {0x0289, 0x0244}, + {0x028c, 0x0245}, + {0x0292, 0x01b7}, + {0x02b3, 0x1d3f}, + {0x02b7, 0x1d42}, + {0x02e1, 0x1d38}, + {0x0377, 0x0376}, + {0x03ac, 0x0386}, + {0x03ac, 0x0386}, + {0x03cc, 0x038c}, + {0x03cc, 0x038c}, + {0x03cd, 0x038e}, + {0x03ce, 0x038f}, + {0x03f8, 0x03f7}, + {0x03fb, 0x03fa}, + {0x1d43, 0x1d2c}, + {0x1d47, 0x1d2e}, + {0x1d4d, 0x1d33}, + {0x1d4f, 0x1d37}, + {0x1d50, 0x1d39}, + {0x1d52, 0x1d3c}, + {0x1d56, 0x1d3e}, + {0x1d5b, 0x2c7d}, + {0x1d79, 0xa77d}, + {0x1d7d, 0x2c63}, + {0x1f70, 0x1fba}, + {0x1f76, 0x1fda}, + {0x1f78, 0x1ff8}, + {0x1f7a, 0x1fea}, + {0x1f7c, 0x1ffa}, + {0x1fe5, 0x1fec}, + {0x2113, 0x2112}, + {0x212f, 0x2130}, + {0x213c, 0x213f}, + {0x213d, 0x213e}, + {0x2146, 0x2145}, + {0x214e, 0x2132}, + {0x2c61, 0x2c60}, + {0x2c65, 0x023a}, + {0x2c66, 0x023e}, + {0x2c73, 0x2c72}, + {0x2c76, 0x2c75}, + {0xa78c, 0xa78b}, + {0xa791, 0xa790}, + {0x1d4b6, 0x1d49c}, + {0x1d4c3, 0x1d4a9}, + {0x1d560, 0x1d546}, + {0x1d7cb, 0x1d7ca}, + {0x1f521, 0x1f520}, +}; + diff --git a/gizmo/text/words.h b/gizmo/text/words.h new file mode 100644 index 0000000..f976a19 --- /dev/null +++ b/gizmo/text/words.h @@ -0,0 +1,21 @@ +#ifndef __WORDS_H__ +#define __WORDS_H__ + +#include +#include + + +typedef std::function +WordsCallback; + + +enum class WordId +{ + None = -1, + Sub = 0, + Ref = 1, +}; + +#endif diff --git a/gizmo/text/wordsqueue.cpp b/gizmo/text/wordsqueue.cpp new file mode 100644 index 0000000..e266f21 --- /dev/null +++ b/gizmo/text/wordsqueue.cpp @@ -0,0 +1,69 @@ +#include "text/wordsqueue.h" + +using namespace std; + +WordsQueue::WordsQueue() +{} + +WordsQueue::~WordsQueue() +{ + release(); +} + +void WordsQueue::push(WordId id, const string &word, double time) +{ + { + unique_lock lock(m_mutex); + m_queue.push(Entry(id, word, time)); + } + m_sem.post(); +} + +WordId WordsQueue::pop(string &word, double &time) +{ + WordId id = WordId::None; + + unique_lock lock(m_mutex); + if (m_queue.empty()) + { + lock.unlock(); + m_sem.wait(); + lock.lock(); + } + + if (!m_queue.empty()) + { + const Entry &e = m_queue.front(); + id = e.id; + word = e.word; + time = e.time; + m_queue.pop(); + } + + return id; +} + +void WordsQueue::release() +{ + m_sem.post(); +} + +size_t WordsQueue::size() const +{ + unique_lock lock(m_mutex); + return m_queue.size(); +} + +bool WordsQueue::empty() const +{ + unique_lock lock(m_mutex); + return m_queue.empty(); +} + +WordsQueue::Entry::Entry() : id(WordId::None) +{} + +WordsQueue::Entry::Entry(WordId id, const string &word, double time) + : id(id), word(word), time(time) +{} + diff --git a/gizmo/text/wordsqueue.h b/gizmo/text/wordsqueue.h new file mode 100644 index 0000000..e26ce25 --- /dev/null +++ b/gizmo/text/wordsqueue.h @@ -0,0 +1,40 @@ +#ifndef __WORDSQUEUE_H__ +#define __WORDSQUEUE_H__ + +#include "words.h" +#include "general/sem.h" +#include +#include + + +class WordsQueue +{ + private: + struct Entry + { + WordId id; + std::string word; + double time; + + Entry(); + Entry(WordId id, const std::string &word, double time); + }; + + public: + WordsQueue(); + ~WordsQueue(); + + void push(WordId id, const std::string &word, double time); + WordId pop(std::string &word, double &time); + void release(); + + size_t size() const; + bool empty() const; + + private: + std::queue m_queue; + mutable std::mutex m_mutex; + Semaphore m_sem; +}; + +#endif diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f1fb530 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +certifi==2018.4.16 +chardet==3.0.4 +cx-Freeze==5.1.1 +idna==2.7 +pybind11==2.2.3 +pycryptodome==3.6.6 +Pypubsub==4.0.0 +pysubs2==0.2.3 +requests==2.19.1 +six==1.11.0 +urllib3==1.23 +wxPython==4.0.3 diff --git a/subsync/.gitignore b/subsync/.gitignore new file mode 100644 index 0000000..64da3ff --- /dev/null +++ b/subsync/.gitignore @@ -0,0 +1,6 @@ +*.pyc +*_layout.py +config.py +version.py +/build +/dist diff --git a/subsync/Makefile b/subsync/Makefile new file mode 100644 index 0000000..c4d8f5e --- /dev/null +++ b/subsync/Makefile @@ -0,0 +1,49 @@ +PYTHON_PREFIX ?= python + +WXFORMBUILDER ?= wxformbuilder +UPDATE_TRANSLATIONS ?= PYTHONPATH=. $(PYTHON_PREFIX) ../tools/update_translations.py + +GUI_SRC = \ + gui/mainwin.fbp \ + gui/subpanel.fbp \ + gui/openwin.fbp \ + gui/syncwin.fbp \ + gui/settingswin.fbp \ + gui/downloadwin.fbp \ + gui/aboutwin.fbp \ + gui/errorwin.fbp \ + +GUI_DST = $(GUI_SRC:.fbp=_layout.py) + + +all: gui-gen config.py version.py + +clean: gui-gen-clean version-clean + + +gui-gen: $(GUI_DST) + +gui-gen-clean: + $(RM) $(GUI_DST) + $(RM) $(GUI_DST:.py=.py.tmp) + +$(GUI_DST): %_layout.py: %.fbp + $(WXFORMBUILDER) -g $< + + +config.py: + cp config.py.template $@ + +version.py: .FORCE + $(PYTHON_PREFIX) ../tools/update_version.py $@ + +version-clean: + $(RM) version.py + + +translations: + $(UPDATE_TRANSLATIONS) + + +.PHONY: all clean gui-gen gui-gen-clean version-clean translations +.PHONY: .FORCE diff --git a/subsync/__main__.py b/subsync/__main__.py new file mode 100644 index 0000000..710ce57 --- /dev/null +++ b/subsync/__main__.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import logging +logger = logging.getLogger(__name__) + +import wx +import sys +import argparse +import gettext +import error +import assets +import loggercfg +from settings import settings +from stream import Stream + +from gui.errorwin import showExceptionDlg +from gui.mainwin import MainWin + + +def subsync(): + gettext.install('subsync') + subs, refs, args = parseCmdArgs(sys.argv) + app = wx.App() + + try: + settings().load() + except Exception as err: + showExceptionDlg() + + setupLogger(args) + + if len(sys.argv) > 1: + logger.info('command line parameters: %s', args) + + if args.window_size: + settings().set(windowSize=args.window_size) + + assets.init(autoUpdate=settings().autoUpdate) + + try: + win = MainWin(None, subs=subs, refs=refs) + win.Show() + + if args.auto: + win.onButtonStartClick(None) + + app.MainLoop() + + except error.Error as err: + showExceptionDlg() + + settings().save() + assets.terminate() + loggercfg.terminate() + + +def setupLogger(args): + level = parseLogLevel(args.loglevel) + if level == None: + level = settings().logLevel + + path = args.logfile + if path == None: + path = settings().logFile + + loggercfg.init(level=level, path=path) + + blacklist = settings().logBlacklist + if blacklist: + loggercfg.setBlacklistFilters(blacklist) + + +def parseLogLevel(level, default=logging.WARNING): + for name in [ 'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' ]: + if level == name: + return getattr(logging, name) + try: + return int(level) + except: + return None + + +def parseCmdArgs(argv): + subs = None + refs = None + + parser = argparse.ArgumentParser(description=_('Subtitle Speech Synchronizer')) + + parser.add_argument('--sub', '--sub-file', type=str, help='subtitle file') + parser.add_argument('--sub-stream', type=int, help='subtitle stream ID') + parser.add_argument('--sub-lang', type=str, help='subtitle language') + parser.add_argument('--sub-enc', type=str, help='subtitle character encoding') + + parser.add_argument('--ref', '--ref-file', type=str, help='reference file') + parser.add_argument('--ref-stream', type=int, help='reference stream ID') + parser.add_argument('--ref-lang', type=str, help='reference language') + parser.add_argument('--ref-enc', type=str, help='reference character encoding (for subtitle references)') + parser.add_argument('--ref-fps', type=float, help='reference framerate') + + parser.add_argument('--loglevel', type=str, help='set logging level, numerically or by name') + parser.add_argument('--logfile', type=str, help='dump logs to specified file') + + parser.add_argument('--window-size', type=int, help='maximum timestamp adjustement (in seconds)') + parser.add_argument('--auto', action='store_true', help='start synchronization automatically') + + args = parser.parse_args() + + if args.sub: + subs = Stream(path=args.sub) + if args.sub_stream != None: + subs.select(args.sub_stream - 1) + subs.setNotNone(lang=args.sub_lang, enc=args.sub_enc) + + if args.ref: + refs = Stream(path=args.ref) + if args.ref_stream != None: + refs.select(args.ref_stream - 1) + refs.setNotNone(lang=args.ref_lang, enc=args.ref_enc, fps=args.ref_fps) + + return subs, refs, args + + +if __name__ == "__main__": + subsync() + diff --git a/subsync/assets/__init__.py b/subsync/assets/__init__.py new file mode 100644 index 0000000..42165ad --- /dev/null +++ b/subsync/assets/__init__.py @@ -0,0 +1,45 @@ +import config +import utils +from assets.assets import Assets +from assets.updater import Updater +from assets.downloader import Downloader + +assets = Assets() +updater = Updater() +updateDownloader = None + + +def init(autoUpdate): + + def onAssetsUpdated(err): + global updateDownloader + if config.assetupd: + cv = utils.getCurrentVersion() + asset = getRemoteAsset(**config.assetupd) + if cv and asset and not isUpdateDownloadInProgress(): + rv = utils.parvseVersion(asset.get('version', None)) + if updater.version == None or updater.version < rv: + if not updater.upgradeReady and rv and rv > cv: + updateDownloader = Downloader(**asset) + if autoUpdate: + updateDownloader.start(name='Updater', daemon=False) + + assets.update(onFinish=onAssetsUpdated, delay=5) + + +def terminate(): + if updateDownloader: + updateDownloader.stop(block=True) + + +def getLocalAsset(*args, **kwargs): + return assets.getLocalAsset(*args, **kwargs) + + +def getRemoteAsset(*args, **kwargs): + return assets.getRemoteAsset(*args, **kwargs) + + +def isUpdateDownloadInProgress(): + return updateDownloader and updateDownloader.isAlive() + diff --git a/subsync/assets/assets.py b/subsync/assets/assets.py new file mode 100644 index 0000000..c584b22 --- /dev/null +++ b/subsync/assets/assets.py @@ -0,0 +1,111 @@ +import config +import thread +import utils +from error import Error +import requests +import json +import threading +import time +import itertools +import os + +import logging +logger = logging.getLogger(__name__) + + +class Assets(object): + def __init__(self): + self.assets = {} + self.lock = threading.Lock() + self.updateThread = thread.Thread( + target=self.updateJob, done=self.onFinish, error=self.onFinish) + self.onFinish = None + self.loadList() + + def getLocalAsset(self, type, params, permutable=False, raiseIfMissing=False): + if permutable: + paramsPerm = itertools.permutations(params) + else: + paramsPerm = [ params ] + + for params in paramsPerm: + fname = '{}.{}'.format('-'.join(params), type) + path = os.path.join(config.assetdir, type, fname) + if os.path.isfile(path): + return path + + if raiseIfMissing: + raise Error(_('Missing {}').format(getAssetPrettyName(type, params)), + type=type, params=params) + + def getRemoteAsset(self, type, params, permutable=False, raiseIfMissing=False): + if permutable: + paramsPerm = itertools.permutations(params) + else: + paramsPerm = [ params ] + + for params in paramsPerm: + assetId = '{}/{}'.format(type, '-'.join(params)) + with self.lock: + if assetId in self.assets: + asset = self.assets[assetId] + asset['title'] = getAssetPrettyName(type, params) + return asset + + if raiseIfMissing: + raise Error(_('Missing {}').format(getAssetPrettyName(type, params)), + type=type, params=params) + + def update(self, delay=None, onFinish=None): + self.onFinish = onFinish + self.updateThread.start(name='Assets', daemon=True, kwargs={'delay':delay}) + + def updateJob(self, delay=None): + if delay: + time.sleep(delay) + + try: + assets = requests.get(config.assetsurl).json() + with self.lock: + self.assets = assets + except Exception as e: + logger.error('cannto get assets list from %s: %r', config.assetsurl, e) + + yield + with self.lock: + self.saveList() + + def onFinish(self, err=None): + with self.lock: + onFinish = self.onFinish + if onFinish: + onFinish(err) + + def loadList(self): + try: + if os.path.isfile(config.assetspath): + with open(config.assetspath, encoding='utf8') as fp: + assets = json.load(fp) + self.assets = assets + except Exception as e: + logger.error('cannot load assets list from %s: %r', config.assetspath, e) + + def saveList(self): + try: + with open(config.assetspath, 'w', encoding='utf8') as fp: + json.dump(self.assets, fp, indent=4) + except Exception as e: + logger.error('cannot write assets list to %s: %r', config.assetspath, e) + + +def getAssetPrettyName(type, params, **kw): + if type == 'speech': + return _('{} speech recognition model').format( + utils.getLanguageName(params[0])) + elif type == 'dict': + return _('dictionary {} / {}').format( + utils.getLanguageName(params[0]), + utils.getLanguageName(params[1])) + else: + return '{}/{}'.format(type, '-'.join(params)) + diff --git a/subsync/assets/downloader.py b/subsync/assets/downloader.py new file mode 100644 index 0000000..c82f1db --- /dev/null +++ b/subsync/assets/downloader.py @@ -0,0 +1,130 @@ +import config +import utils +import thread +import pubkey +import error +import requests +import tempfile +import zipfile +import collections +import Crypto +import os +import sys + +import logging +logger = logging.getLogger(__name__) + + +DownloaderState = collections.namedtuple('DownloaderState', + ['action', 'progress', 'error', 'terminated']) + + +class Downloader(thread.Thread): + def __init__(self, type=None, url=None, sig=None, version=None, size=None, **kw): + super().__init__() + self.type = type + self.url = url + self.sig = sig + self.version = utils.parvseVersion(version) + self.size = size + self.fp = None + self.hash = Crypto.Hash.SHA256.new() + self.state = thread.AtomicValue() + + def run(self): + yield from self.validate() + yield from self.download() + yield from self.verify() + yield from self.install() + + def setState(self, action, progress=None, error=None, terminated=False): + state = DownloaderState(action, progress, error, terminated) + self.state.set(state) + + def getState(self): + return self.state.get() + + def validate(self): + for key, val in dict(type=self.type, url=self.url, sig=self.sig).items(): + if val == None: + raise error.Error('Invalid asset data, missing parameter', key=key) + yield + + def download(self): + logger.info('downloading %s', self.url) + self.setState('download') + + try: + req = requests.get(self.url, stream=True) + req.raise_for_status() + self.fp = tempfile.SpooledTemporaryFile(max_size=64*1024*1024) + yield + + pos = 0 + size = getSizeFromHeader(req.headers, self.size) + + for chunk in req.iter_content(8192): + self.fp.write(chunk) + self.hash.update(chunk) + pos += len(chunk) + self.setState('download', progress=(pos, size)) + yield + + except Exception as e: + raise error.Error(_('Asset download failed'), details=str(e), + type=self.type, url=self.url) + + logger.info('successfully downloaded %s', self.url) + + def verify(self): + logger.info('verifying signature') + self.setState('verify', progress=1.0) + + sig = None + + try: + req = requests.get(self.sig) + req.raise_for_status() + sig = req.content + + except Exception as e: + raise error.Error(_('Signature download failed'), details=str(e), + type=self.type, url=self.sig) + + if not pubkey.getVerifier().verify(self.hash, sig): + self.fp.close() + self.fp = None + raise error.Error(_('Signature verification failed'), url=self.url) + yield + + def install(self): + logger.info('installing %s', self.url) + self.setState('install', progress=1.0) + + if self.type == 'zip': + dstdir = config.assetdir + logger.info('extracting zip asset to %s', dstdir) + os.makedirs(dstdir, exist_ok=True) + zipf = zipfile.ZipFile(self.fp) + zipf.extractall(dstdir) + + else: + raise error.Error('Invalid asset type', type=self.type, url=self.url) + + self.fp.close() + self.fp = None + yield + + def done(self): + self.setState('done', progress=1.0, terminated=True) + + def error(self, err): + self.setState('error', error=sys.exc_info(), terminated=True) + + +def getSizeFromHeader(headers, defaultSize=None): + try: + return int(headers.get('content-length')) + except: + return defaultSize + diff --git a/subsync/assets/updater.py b/subsync/assets/updater.py new file mode 100644 index 0000000..7b0cd12 --- /dev/null +++ b/subsync/assets/updater.py @@ -0,0 +1,61 @@ +import config +import utils +import json +import shutil +import os +import subprocess +import stat + +import logging +logger = logging.getLogger(__name__) + + +class Updater(object): + def __init__(self): + self.upgradeReady = False + self.install = None + self.version = None + self.load() + + def load(self): + dirPath = os.path.join(config.assetdir, 'upgrade') + path = os.path.join(dirPath, 'upgrade.json') + + if os.path.isdir(dirPath): + try: + with open(path, encoding='utf8') as fp: + upgrade = json.load(fp) + + self.install = os.path.join(dirPath, upgrade['install']) + self.version = utils.parvseVersion(upgrade['version']) + + currentVersion = utils.getCurrentVersion() + if currentVersion and self.version > currentVersion: + self.upgradeReady = True + else: + self.upgradeReady = False + self.remove() + + except Exception as e: + logger.error('invalid upgrade description, %r', e) + self.remove() + + def remove(self): + self.upgradeReady = False + path = os.path.join(config.assetdir, 'upgrade') + try: + shutil.rmtree(path, ignore_errors=True) + except Exception as e: + logger.error('cannot remove upgrade data from %s: %r', path, e) + + def upgrade(self): + cwd = os.path.join(config.assetdir, 'upgrade') + path = os.path.join(cwd, self.install) + logger.info('executing installer %s', path) + + mode = os.stat(path).st_mode + if (mode & stat.S_IEXEC) == 0: + os.chmod(path, mode | stat.S_IEXEC) + subprocess.Popen(path, cwd=cwd) + self.upgradeReady = False + diff --git a/subsync/config.py.template b/subsync/config.py.template new file mode 100644 index 0000000..34191a8 --- /dev/null +++ b/subsync/config.py.template @@ -0,0 +1,30 @@ +import os +import sys + +appname = 'subsync' + + +if sys.platform == 'win32': + configdir = os.path.join(os.environ['APPDATA'], appname) + shareddir = os.path.join(os.environ['ALLUSERSPROFILE'], appname) + assetupd = dict(type='subsync', params=('win', 'x86_64')) + +elif sys.platform == 'linux': + configdir = os.path.join(os.path.expanduser('~'), '.config', appname) + shareddir = configdir + assetupd = None + +else: + configdir = '.' + shareddir = configdir + assetupd = None + +configpath = os.path.join(configdir, appname + '.json') +assetspath = os.path.join(configdir, 'assets.json') + +assetdir = os.path.join(shareddir, 'assets') +imgdirs = [ os.path.join(d, 'img') for d in ('.', 'subsync') ] +keypaths = [ os.path.join(d, 'key.pub') for d in ('.', 'subsync') ] + +assetsurl = 'assetsurl = 'https://sc0ty.github.io/subsync/update/assets.json' + diff --git a/subsync/data/__init__.py b/subsync/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subsync/data/charenc.py b/subsync/data/charenc.py new file mode 100644 index 0000000..1869cd7 --- /dev/null +++ b/subsync/data/charenc.py @@ -0,0 +1,46 @@ +charEncodings = ( + ("UTF-8", "Universal" ), + ("UTF-16", "Universal" ), + ("UTF-16BE", "Universal, big endian" ), + ("UTF-16LE", "Universal, little endian" ), + ("GB18030", "Universal, Chinese" ), + + ("Windows-1250", "Eastern European" ), + ("Windows-1251", "Cyrillic" ), + ("Windows-1252", "Western European" ), + ("Windows-1253", "Greek" ), + ("Windows-1254", "Turkish" ), + ("Windows-1255", "Hebrew" ), + ("Windows-1256", "Arabic" ), + ("Windows-1257", "Baltic" ), + ("Windows-1258", "Vietnamese" ), + ("Windows-874", "Thai" ), + + ("ISO-8859-2", "Eastern European Latin-2" ), + ("ISO-8859-3", "Esperanto Latin-3" ), + ("ISO-8859-6", "Arabic" ), + ("ISO-8859-7", "Greek" ), + ("ISO-8859-8", "Hebrew" ), + ("ISO-8859-9", "Turkish" ), + ("ISO-8859-10", "Nordic Latin-6" ), + ("ISO-8859-11", "Thai" ), + ("ISO-8859-13", "Baltic Latin-7" ), + ("ISO-8859-14", "Celtic Latin-8" ), + ("ISO-8859-15", "Western European Latin-9" ), + ("ISO-8859-16", "South-Eastern European Latin-10"), + ("ISO-2022-CN-EXT", "Simplified Chinese" ), + ("ISO-2022-JP-2", "Japanese" ), + ("ISO-2022-KR", "Korean" ), + ("ISO-2022-TW", "Traditional Chinese Unix" ), + + ("IBM850", "Western European" ), + ("KOI8-R", "Russian" ), + ("KOI8-U", "Ukrainian" ), + ("EUC-CN", "Simplified Chinese Unix" ), + ("EUC-JP", "Japanese Unix" ), + ("Shift_JIS", "Japanese" ), + ("CP949", "Korean" ), + ("Big5", "Traditional Chinese" ), + ("Big5-HKSCS", "Hong-Kong Supplementary" ), + ("VISCII", "Vietnamese" ), +) diff --git a/subsync/data/filetypes.py b/subsync/data/filetypes.py new file mode 100644 index 0000000..bd4e270 --- /dev/null +++ b/subsync/data/filetypes.py @@ -0,0 +1,16 @@ +subtitleTypes = ( + ('SubRIP', '*.srt'), + ('MicroDVD', '*.txt'), + ('Sub Station Alpha', '*.ssa'), + ('Advanced SSA', '*.ass')) + +videoExt = ( + '*.webm', '*.mkv', '*.flv', '*.vob', '*.ogv', '*.avi', '*.mov', + '*.qt', '*.wmv', '*.rm', '*.rmvb', '*.asf', '*.amv', '*.mp4', + '*.m4p', '*.m4v', '*.mpg', '*.mp2', '*.mpeg', '*.mpe', '*.mpv', + '*.mpg', '*.mpeg', '*.m2v', '*.m4v', '*.3gp', '*.3g2', '*.f4v', + '*.f4p', '*.f4a', '*.f4b') + +subtitleWildcard = ';'.join(x[1] for x in subtitleTypes) +videoWildcard = ';'.join(videoExt) + diff --git a/subsync/data/languages.py b/subsync/data/languages.py new file mode 100644 index 0000000..aaeb4bf --- /dev/null +++ b/subsync/data/languages.py @@ -0,0 +1,46 @@ +languages = { + 'alb': ('Albanian', ['Windows-1252', 'ISO-8859-1']), + 'ara': ('Arabic', ['ISO-8859-6']), + 'bel': ('Belarusian', ['ISO-8859-5']), + 'bul': ('Bulgarian', ['Windows-1251', 'ISO-8859-5']), + 'cat': ('Catalan', ['Windows-1252', 'ISO-8859-1']), + 'chi': ('Chinese', ['Windows-950']), + 'cze': ('Czech', ['Windows-1250', 'ISO-8859-2']), + 'dan': ('Danish', ['Windows-1252', 'ISO-8859-1']), + 'dut': ('Dutch', ['Windows-1252', 'ISO-8859-1']), + 'eng': ('English', ['Windows-1252', 'ISO-8859-1']), + 'est': ('Estonian', ['ISO-8859-15']), + 'fao': ('Faroese', ['Windows-1252', 'ISO-8859-1']), + 'fin': ('Finnish', ['Windows-1252', 'ISO-8859-1']), + 'fre': ('French', ['Windows-1252', 'ISO-8859-1']), + 'ger': ('German', ['Windows-1252', 'ISO-8859-1']), + 'gla': ('Gaelic', ['Windows-1252', 'ISO-8859-1']), + 'glg': ('Galician', ['Windows-1252', 'ISO-8859-1']), + 'gre': ('Greek', ['Windows-1253', 'ISO-8859-7']), + 'heb': ('Hebrew', ['Windows-1255', 'ISO-8859-8']), + 'hrv': ('Croatian', ['Windows-1250', 'ISO-8859-2']), + 'hun': ('Hungarian', ['Windows-1250', 'ISO-8859-2']), + 'ice': ('Icelandic', ['Windows-1252', 'ISO-8859-1']), + 'ind': ('Indonesian', ['Windows-1252', 'ISO-8859-1']), + 'ita': ('Italian', ['Windows-1252', 'ISO-8859-1']), + 'jpn': ('Japanese', ['Windows-932', 'ISO-2022-JP-2', 'EUC-JP']), + 'kor': ('Korean', ['Windows-949']), + 'lat': ('Latin', []), + 'lav': ('Latvian', ['Windows-1257', 'ISO-8859-13']), + 'lit': ('Lithuanian', ['Windows-1257', 'ISO-8859-13']), + 'may': ('Malay', ['Windows-1252', 'ISO-8859-1']), + 'mlg': ('Malagasy', []), + 'mlt': ('Maltese', ['ISO-8859-3']), + 'nor': ('Norwegian', ['Windows-1252', 'ISO-8859-1']), + 'pol': ('Polish', ['Windows-1250', 'ISO-8859-2']), + 'por': ('Portuguese', ['Windows-1252', 'ISO-8859-1']), + 'rum': ('Romanian', ['Windows-1250', 'ISO-8859-2']), + 'rus': ('Russian', ['Windows-1251', 'ISO-8859-5', 'KOI8-R']), + 'slo': ('Slovak', ['Windows-1250', 'ISO-8859-2']), + 'slv': ('Slovenian', ['Windows-1250', 'ISO-8859-2']), + 'spa': ('Spanish', ['Windows-1252', 'ISO-8859-1']), + 'srp': ('Serbian', ['Windows-1250', 'ISO-8859-2', 'Windows-1251', 'ISO-8859-5']), + 'swe': ('Swedish', ['Windows-1252', 'ISO-8859-1']), + 'tur': ('Turkish', ['Windows-1254', 'ISO-8859-9']), + 'ukr': ('Ukrainian', ['Windows-1251', 'ISO-8859-5']), +} diff --git a/subsync/dictionary.py b/subsync/dictionary.py new file mode 100644 index 0000000..ae26b65 --- /dev/null +++ b/subsync/dictionary.py @@ -0,0 +1,45 @@ +import gizmo +from assets import assets +from error import Error +import os + +import logging +logger = logging.getLogger(__name__) + + +def loadDictionary(lang1, lang2, minLen=0): + dictionary = gizmo.Dictionary() + + dictPath = assets.getLocalAsset('dict', (lang1, lang2)) + if dictPath: + for key, val in loadDictionaryFromFile(dictPath): + if len(key) >= minLen and len(val) >= minLen: + dictionary.add(key, val) + + else: + dictPath = assets.getLocalAsset('dict', (lang2, lang1)) + if dictPath: + for key, val in loadDictionaryFromFile(dictPath): + if len(key) >= minLen and len(val) >= minLen: + dictionary.add(val, key) + + if not dictPath: + raise Error(_('There is no dictionary for transaltion from {} to {}') + .format(lang1, lang2)) \ + .add('language1', lang1) \ + .add('language2', lang2) + + logger.info('dictionary ready with %u entries', dictionary.size()) + return dictionary + + +def loadDictionaryFromFile(path): + logger.info('loading dictionary: "%s"', path) + with open(path, 'r', encoding='utf8') as fp: + for line in fp: + if len(line) > 0 and line[0] != '#': + ents = line.strip().split('|') + if len(ents) >= 2: + key = ents[0].lower() + for val in ents[1:]: + yield (key, val.lower()) diff --git a/subsync/encdetect.py b/subsync/encdetect.py new file mode 100644 index 0000000..cf3073c --- /dev/null +++ b/subsync/encdetect.py @@ -0,0 +1,31 @@ +import locale +from data.languages import languages +from error import Error + +import logging +logger = logging.getLogger(__name__) + + +def detectEncoding(path, lang, probeSize=32*1024): + dlang, denc = locale.getdefaultlocale() + encs = [ 'UTF-8' ] + languages.get(lang, (None, []))[1] + if denc not in encs: + encs.append(denc) + + # TODO: use also dlang (2-letter code, must be converted to 3-letter) + + try: + for enc in encs: + with open(path, 'r', encoding=enc) as fp: + try: + fp.read(32 * 1024) + logger.info('detected encoding %s for file "%s"', enc, path) + return enc + except UnicodeError: + pass + except FileNotFoundError: + raise Error('File not found').add('path', path) + + logger.info('couldn\'t detect encoding for file "%s", tried %s', path, encs) + return None + diff --git a/subsync/error.py b/subsync/error.py new file mode 100644 index 0000000..624ddbb --- /dev/null +++ b/subsync/error.py @@ -0,0 +1,75 @@ +import collections +import gizmo +import error + +import logging +logger = logging.getLogger(__name__) + + +class Error(Exception): + def __init__(self, msg, **fields): + super(Error, self).__init__(msg) + self.message = msg + self.fields = fields + + def __repr__(self): + return '{}; {}'.format(str(self.message), + '; '.join('{}: {}'.format(k, v) for k, v in self.fields.items())) + + def __str__(self): + return '{}\n{}'.format(self.message, + '\n'.join('{}:\t{}'.format(k, v) for k, v in self.fields.items())) + + def add(self, key, val): + self.fields[key] = val + return self + + def addn(self, key, val): + if val != None: + self.fields[key] = val + return self + + +class ErrorsGroup(object): + def __init__(self, msg): + self.message = msg + self.descriptions = set() + self.errors = [] + self.fields = collections.defaultdict(set) + + def add(self, error): + self.errors.append(error) + if hasattr(error, 'message'): + self.descriptions.add(error.message) + if hasattr(error, 'fields'): + for key, val in error.fields.items(): + self.fields[key].add(val) + + def __repr__(self): + fields = [ ' - {}: {}'.format(k, formatFieldsVals(v)) + for k, v in sorted(self.fields.items()) ] + + return '{}\n{}\n{}'.format( + self.message, + '\n'.join(sorted(self.descriptions)), + '\n'.join(fields)) + + def __len__(self): + return len(self.errors) + + +def formatFieldsVals(v, maxlen=4): + res = '; '.join(sorted(v)[:maxlen]) + if len(v) > maxlen: + res += ' ...' + return res + + +def getExceptionMessage(e): + if type(e) is gizmo.Error: + return str(e).split('\n', 1)[0] + elif type(e) is error.Error: + return e.message + else: + return str(e) + diff --git a/subsync/graph.py b/subsync/graph.py new file mode 100644 index 0000000..426f6aa --- /dev/null +++ b/subsync/graph.py @@ -0,0 +1,112 @@ +import gizmo +import pydot +import os + +import logging +logger = logging.getLogger(__name__) + + +_initialized = False + +def init(): + global _initialized + if _initialized: + return + + def addOutputsGetter(type): + def getter(self): + if hasattr(self, '_connectedOutputs'): + return self._connectedOutputs + else: + return [] + setattr(type, 'getConnectedOutputs', getter) + + + def addCallbackWrapper(type, methodName, edgeName): + orgCb = getattr(type, methodName) + + def wrapper(self, cb, dst=None): + if not hasattr(self, '_connectedOutputs'): + self._connectedOutputs = [] + + if cb: + if hasattr(cb, '__func__') and hasattr(cb.__func__, '__name__'): + name = cb.__func__.__name__ + self._connectedOutputs.append((edgeName, dst, name)) + else: + self._connectedOutputs.append((edgeName, dst)) + + else: + for out in self._connectedOutputs: + if out[0] == edgeName: + self._connectedOutputs.remove(out) + break + + orgCb(self, cb) + + setattr(type, methodName, wrapper) + + addOutputsGetter(gizmo.SubtitleDec) + addCallbackWrapper(gizmo.SubtitleDec, 'connectWordsCallback', 'words') + addCallbackWrapper(gizmo.SubtitleDec, 'connectSubsCallback', 'subtitles') + + addOutputsGetter(gizmo.SpeechRecognition) + addCallbackWrapper(gizmo.SpeechRecognition, 'connectWordsCallback', 'words') + + addOutputsGetter(gizmo.Translator) + addCallbackWrapper(gizmo.Translator, 'connectWordsCallback', 'words') + + _initialized = True + + +def drawNode(graph, node, names={}): + srcName = getNodeName(graph, node, names) + + if hasattr(node, 'getConnectedOutputs'): + outputs = node.getConnectedOutputs() + for output in outputs: + dst = output[1] + newDst = dst not in names + dstName = getNodeName(graph, dst, names) + + labels = {} + if output[0]: + labels['label'] = output[0] + if len(output) >= 3 and output[2]: + labels['headlabel'] = output[2] + + graph.add_edge(pydot.Edge(srcName, dstName, **labels)) + + if newDst: + drawNode(graph, dst, names) + + +def getNodeName(graph, node, names={}): + if node not in names: + i = 0 + prefix = node.__class__.__name__ + while True: + name = prefix + str(i) + if name not in names.values(): + names[node] = name + graph.add_node(pydot.Node(name)) + return name + i += 1 + return names[node] + + +def saveGraph(path, nodes, format=None): + graph = pydot.Dot(graph_type='digraph', rankdir='LR') + graph.set_node_defaults(shape='box') + + for node in nodes: + drawNode(graph, node) + + if format == None: + format = os.path.splitext(path)[1][1:] + if format == 'dot': + format = 'raw' + + logger.info('saving pipeline graph to %s', path) + graph.write(path, format=format) + diff --git a/subsync/gui/__init__.py b/subsync/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subsync/gui/aboutwin.fbp b/subsync/gui/aboutwin.fbp new file mode 100644 index 0000000..eaff468 --- /dev/null +++ b/subsync/gui/aboutwin.fbp @@ -0,0 +1,958 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + aboutwin_layout + 1000 + none + 1 + AbutWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + AboutWin + + + wxDEFAULT_DIALOG_STYLE + ; forward_declare + About + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 0 + 0 + + fgSizer1 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 50 + wxEXPAND + 1 + + 0 + wxBOTH + 0,1 + 0 + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmapLogo + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxLEFT + 1 + + 1 + wxBOTH + + + 0 + + fgSizer3 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,92,24,70,0 + 0 + 0 + wxID_ANY + Subtitle Speech Synchronizer + + 0 + + + 0 + + 1 + m_staticText5 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + custom build + + 0 + + + 0 + + 1 + m_textVersion + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 10 + protected + 0 + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + This is an automatic movie subtitle synchronization tool. Synchronization is done by listening to the audio track, translating it if necessary. It could also use another subtitle as a reference. In other words, just feed it with subtitles and video file and it will figure it out. Author: Michał Szymaniak + + 0 + + + 0 + + 1 + m_staticText10 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 10 + protected + 0 + + + + 5 + wxEXPAND + 1 + + 0 + wxBOTH + 2 + + 0 + + fgSizer3 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + License + + 0 + + + 0 + + 1 + m_buttonLicense + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonLicenseClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Credits + + 0 + + + 0 + + 1 + m_buttonCredits + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonCreditsClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Website + + 0 + + + 0 + + 1 + m_buttonWebsite + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonWebsiteClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + Close + + 0 + + + 0 + + 1 + m_buttonClose + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/aboutwin.py b/subsync/gui/aboutwin.py new file mode 100644 index 0000000..8a35ae0 --- /dev/null +++ b/subsync/gui/aboutwin.py @@ -0,0 +1,718 @@ +import gui.aboutwin_layout +import img +import wx +import wx.lib.dialogs + + +class AboutWin(gui.aboutwin_layout.AboutWin): + def __init__(self, parent): + gui.aboutwin_layout.AboutWin.__init__(self, parent) + img.setItemBitmap(self.m_bitmapLogo, 'logo') + + try: + from version import version + self.m_textVersion.SetLabel(_('version ') + version) + except: + pass + + self.m_buttonClose.SetFocus() + self.Fit() + self.Layout() + + def onButtonLicenseClick(self, event): + dlg = wx.lib.dialogs.ScrolledMessageDialog(self, license, 'License', + size=(500, 800)) + dlg.ShowModal() + + def onButtonCreditsClick(self, event): + dlg = wx.lib.dialogs.ScrolledMessageDialog(self, credits, 'Credits', + size=(600, 300)) + dlg.ShowModal() + + def onButtonWebsiteClick(self, event): + wx.LaunchDefaultBrowser('http://sc0ty.github.io/subsync') + + +credits='''FFmpeg - Copyright (c) the FFmpeg developers - LGPLv2.1 or later +CMU Sphinx - Copyright (c) 1999-2016 Carnegie Mellon University - BSD license +wxWindows - Copyright (c) 1998-2005 Julian Smart, Robert Roebling et al - wxWindows Library Licence +pybind11 - Copyright (c) 2016 Wenzel Jakob - BSD license +pysubs2 - Copyright (c) 2014-2015 Tomas Karabela - MIT license +requests - Copyright (c) 2018 Kenneth Reitz - Apache2 license +Catch - Copyright (c) 2018 Two Blue Cubes Ltd - Boost Software License''' + + +license=''' GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +.''' diff --git a/subsync/gui/choiceenc.py b/subsync/gui/choiceenc.py new file mode 100644 index 0000000..c206646 --- /dev/null +++ b/subsync/gui/choiceenc.py @@ -0,0 +1,24 @@ +import wx +from data.charenc import charEncodings + + +class ChoiceCharEnc(wx.Choice): + def __init__(self, *args, **kwargs): + wx.Choice.__init__(self, *args, **kwargs) + self.Append(_('Auto detect'), None) + for enc in charEncodings: + name = '{} - {}'.format(enc[0], enc[1]) + self.Append(name, enc[0]) + + def setCharEnc(self, enc): + if enc != None: + for i in range(1, self.GetCount()): + if self.GetClientData(i) == enc: + self.SetSelection(i) + return + self.SetSelection(0) + + def getCharEnc(self): + i = self.GetSelection() + return self.GetClientData(i) if i != -1 else None + diff --git a/subsync/gui/choicelang.py b/subsync/gui/choicelang.py new file mode 100644 index 0000000..e002ec9 --- /dev/null +++ b/subsync/gui/choicelang.py @@ -0,0 +1,31 @@ +import wx +from data.languages import languages + + +class ChoiceLang(wx.Choice): + def __init__(self, *args, **kwargs): + wx.Choice.__init__(self, *args, **kwargs) + self.initLangs() + + def initLangs(self): + self.Append(_(''), None) + self.addSortedLangs({ languages[c][0]: c for c in languages.keys() }) + + def addSortedLangs(self, langs): + for name in sorted(langs): + self.Append(name.title(), langs[name]) + + def setLang(self, lang): + if lang != None: + lang = lang.lower() + for i in range(1, self.GetCount()): + if self.GetClientData(i) == lang: + self.SetSelection(i) + return True + self.SetSelection(0) + return False + + def getLang(self): + i = self.GetSelection() + return self.GetClientData(i) if i != -1 else None + diff --git a/subsync/gui/downloadwin.fbp b/subsync/gui/downloadwin.fbp new file mode 100644 index 0000000..ba2a859 --- /dev/null +++ b/subsync/gui/downloadwin.fbp @@ -0,0 +1,720 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + downloadwin_layout + 1000 + none + 1 + DownloadWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + -1,-1 + DownloadWin + + + wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER + ; forward_declare + Download + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 1 + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxEXPAND|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Download + + 0 + + + 0 + + 1 + m_textName + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + 340,-1 + 1 + m_gaugeProgress + 1 + + + protected + 1 + + 100 + Resizable + 1 + + wxGA_HORIZONTAL + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + processing... + + 0 + + + 0 + + 1 + m_textDetails + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + wxEXPAND|wxTOP|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_line + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_RIGHT|wxTOP|wxRIGHT|wxLEFT + 1 + + 0 + wxBOTH + + + 0 + + fgSizer81 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Hide + + 0 + + + 0 + + 1 + m_buttonHide + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonHideClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_CANCEL + Cancel + + 0 + + + 0 + + 1 + m_buttonCancel + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonCancelClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/downloadwin.py b/subsync/gui/downloadwin.py new file mode 100644 index 0000000..9bbfe62 --- /dev/null +++ b/subsync/gui/downloadwin.py @@ -0,0 +1,84 @@ +import gui.downloadwin_layout +import gui.errorwin +import wx +import assets +from utils import fileSizeFmt + + +class DownloadWin(gui.downloadwin_layout.DownloadWin): + def __init__(self, parent, downloader, title, allowHide=False): + super().__init__(parent) + + self.downloader = downloader + self.state = None + + self.m_textName.SetLabel(title) + self.m_buttonHide.Show(allowHide) + self.updateStatus(downloader.getState()) + + self.updateTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.onUpdateTimerTick, self.updateTimer) + self.updateTimer.Start(200) + + def onButtonCancelClick(self, event): + self.downloader.stop() + self.EndModal(wx.ID_CANCEL) + + def onButtonHideClick(self, event): + self.EndModal(wx.ID_CLOSE) + + def onUpdateTimerTick(self, event): + state = self.downloader.getState() + if state and self.state != state: + self.state = state + self.updateStatus(state) + + if state.error and self.IsModal(): + gui.errorwin.showExceptionDlg(self, state.error) + + if state.terminated: + self.updateTimer.Stop() + if state.error == None and self.IsModal(): + self.EndModal(wx.ID_OK) + + def updateStatus(self, state): + msgs = { + 'download': _('downloading'), + 'verify': _('verifying'), + 'install': _('processing'), + 'done': _('done'), + 'error': _('operation failed') } + + msg = msgs.get(state.action, _('error')) + self.setDetailMessage(msg, state.progress, not state.terminated) + self.setProgress(state.progress) + + def setDetailMessage(self, msg, progress, appendProgress=True): + if appendProgress: + if type(progress) is tuple: + pos, size = progress + if size != None and size != 0: + msg += ' {} / {}'.format(fileSizeFmt(pos), fileSizeFmt(size)) + else: + msg += ' {}'.format(fileSizeFmt(pos)) + else: + msg += '...' + self.m_textDetails.SetLabel(msg) + + def setProgress(self, progress): + pr = None + if type(progress) is tuple: + pos, size = progress + if size != None and size != 0: + self.m_gaugeProgress.SetValue(min(100, int(100.0 * pos / size))) + else: + self.m_gaugeProgress.Pulse() + elif progress != None: + self.m_gaugeProgress.SetValue(min(100, int(100.0 * progress))) + + +class UpdateWin(DownloadWin): + def __init__(self, parent, allowHide=False): + title = _('Application upgrade') + super().__init__(parent, assets.updateDownloader, title, allowHide) + diff --git a/subsync/gui/errorwin.fbp b/subsync/gui/errorwin.fbp new file mode 100644 index 0000000..725bb63 --- /dev/null +++ b/subsync/gui/errorwin.fbp @@ -0,0 +1,649 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + errorwin_layout + 1000 + none + 1 + ErrorWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + ErrorWin + + + wxDEFAULT_DIALOG_STYLE + ; forward_declare + Error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer2 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + + + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxEXPAND + 1 + + 2 + wxBOTH + + + 0 + + fgSizer1 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_WARNING; wxART_MESSAGE_BOX + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmapIcon + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + 0 + + 1 + wxBOTH + + + 0 + + fgSizer3 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,90,-1,70,0 + 0 + 0 + wxID_ANY + Error + + 0 + + + 0 + + 1 + m_textMsg + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + wxSYS_COLOUR_HIGHLIGHT + 1 + + 0 + 0 + wxID_ANY + [details] + + 0 + + + 0 + + 1 + m_textDetails + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + onTextDetailsClick + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxALL|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelFields + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer3 + wxVERTICAL + none + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + OK + + 0 + + + 0 + + 1 + m_button1 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/errorwin.py b/subsync/gui/errorwin.py new file mode 100644 index 0000000..004b8c3 --- /dev/null +++ b/subsync/gui/errorwin.py @@ -0,0 +1,68 @@ +import wx +import wx.lib.dialogs +import gui.errorwin_layout +import error +import traceback +import sys +from functools import wraps + +import logging +logger = logging.getLogger(__name__) + + +class ErrorWin(gui.errorwin_layout.ErrorWin): + def __init__(self, parent, msg): + super().__init__(parent) + self.m_textMsg.SetLabel(msg) + self.m_textMsg.Wrap(600) + self.Fit() + self.Layout() + self.Centre(wx.BOTH) + + self.msg = msg + self.details = [ msg, '\n\n' ] + + def addDetails(self, *args): + self.details += args + + def onTextDetailsClick(self, event): + dlg = wx.lib.dialogs.ScrolledMessageDialog(self, ''.join(self.details), + _('Error'), size=(800, 500), style=wx.DEFAULT_FRAME_STYLE) + font = wx.Font(wx.NORMAL_FONT.GetPointSize(), wx.FONTFAMILY_TELETYPE, + wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) + dlg.SetFont(font) + dlg.ShowModal() + + +def showExceptionDlg(parent=None, excInfo=None): + if excInfo: + type, exc, tb = excInfo + else: + type, exc, tb = sys.exc_info() + + if exc: + msg = error.getExceptionMessage(exc) + with ErrorWin(parent, msg) as dlg: + dlg.addDetails(*traceback.format_exception(type, exc, tb)) + dlg.ShowModal() + + +def error_dlg(func): + '''Catch exceptions of type error.Error and gizmo.Error + and show them in error window + ''' + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as err: + logger.warn('error_dlg: %r', err, exc_info=True) + + if len(args) > 0 and isinstance(args[0], wx.Window): + parent = args[0] + else: + parent = None + + showExceptionDlg(parent) + return wrapper + diff --git a/subsync/gui/filedlg.py b/subsync/gui/filedlg.py new file mode 100644 index 0000000..552350d --- /dev/null +++ b/subsync/gui/filedlg.py @@ -0,0 +1,46 @@ +import wx +import os +from settings import settings + + +def showOpenFileDlg(parent, **args): + style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST + if 'defaultFile' not in args: + args['defaultDir'] = settings().lastdir + + with wx.FileDialog(parent, _('Select file'), style=style, **args) as fileDialog: + if fileDialog.ShowModal() == wx.ID_OK: + path = fileDialog.GetPath() + settings().lastdir = os.path.dirname(path) + return path + + +def appendExtensionToPath(dlg): + wildcard = dlg.GetWildcard().split('|') + path = dlg.GetPath() + i = 2 * dlg.GetFilterIndex() + 1 + + if i >= 0 and i < len(wildcard): + exts = wildcard[i].split(';') + if len(exts) == 0: + return path + + for ext in exts: + if ext == '*.*' or ext == '*' or path.lower().endswith(ext[1:]): + return path + return path + ext[1:] + else: + return path + + +def showSaveFileDlg(parent, **args): + style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT + if 'defaultFile' not in args: + args['defaultDir'] = settings().lastdir + + with wx.FileDialog(parent, _('Select file'), style=style, **args) as fileDialog: + if fileDialog.ShowModal() == wx.ID_OK: + path = appendExtensionToPath(fileDialog) + settings().lastdir = os.path.dirname(path) + return path + diff --git a/subsync/gui/filedrop.py b/subsync/gui/filedrop.py new file mode 100644 index 0000000..687eea6 --- /dev/null +++ b/subsync/gui/filedrop.py @@ -0,0 +1,28 @@ +import wx + + +class FileDropTarget(wx.FileDropTarget): + def __init__(self, dropTarget, multiple=False): + wx.FileDropTarget.__init__(self) + self.dropTarget = dropTarget + self.multiple = multiple + + def OnDropFiles(self, x, y, filenames): + if self.dropTarget != None: + if self.multiple: + return self.dropTarget(filenames) + elif len(filenames) > 0: + return self.dropTarget(filenames[0]) + return False + + +def setFileDropTarget(target, callback, childrens=True, multiple=False): + target.SetDropTarget(FileDropTarget(callback, multiple)) + + if childrens: + for child in target.GetChildren(): + if isinstance(child, wx.Window): + # separate instance of FileDropTarget since will take ownership + # of it and will destroy it at close + child.SetDropTarget(FileDropTarget(callback, multiple)) + diff --git a/subsync/gui/mainwin.fbp b/subsync/gui/mainwin.fbp new file mode 100644 index 0000000..fcbf5c5 --- /dev/null +++ b/subsync/gui/mainwin.fbp @@ -0,0 +1,1323 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + mainwin_layout + 1000 + none + 1 + MainWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + -1,-1 + MainWin + + -1,-1 + wxDEFAULT_FRAME_STYLE + ; forward_declare + Subtitle Speech Synchronizer + + + Subtitle Speech Synchronizer + wxTAB_TRAVERSAL + 1 + + + + + + + + + + onClose + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer2 + wxVERTICAL + none + + 5 + wxEXPAND|wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel2 + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 5 + 0 + + fgSizer1 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxEXPAND|wxTOP|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Subtitles: + + 0 + + + 0 + + 1 + m_staticText3 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelSub + 1 + + + protected + 1 + + Resizable + 1 + + SubtitlePanel; gui.subpanel; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxTOP|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + References (your video): + + 0 + + + 0 + + 1 + m_staticText31 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelRef + 1 + + + protected + 1 + + Resizable + 1 + + SubtitlePanel; gui.subpanel; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxTOP|wxBOTTOM + 1 + + 0 + wxBOTH + 1 + 0 + 0 + + fgSizer5 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Max adjustment + + 0 + + + 0 + + 1 + m_staticText32 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 180 + + 0 + + 5 + + 0 + + 1 + m_sliderMaxDist + 1 + + + protected + 1 + + Resizable + 1 + + wxSL_HORIZONTAL + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSliderMaxDistScroll + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL|wxEXPAND|wxFIXED_MINSIZE + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 999 min + + 0 + + + 0 + + 1 + m_textMaxDist + 1 + + + protected + 1 + + Resizable + 1 + + wxST_NO_AUTORESIZE + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline1 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_RIGHT|wxBOTTOM + 1 + + 0 + wxBOTH + + + 0 + + fgSizer8 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Menu + + 0 + + + 0 + + 1 + m_buttonMenu + 1 + + + protected + 1 + + Resizable + 1 + + wxBU_EXACTFIT + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonMenuClick + + + + + + + + + + + + + + + + + + + + + + + + + Menu + m_menu + protected + + + 0 + 1 + + wxID_PROPERTIES + wxITEM_NORMAL + Settings + m_menuItemSettings + none + + + onMenuItemSettingsClick + + + + m_separator1 + none + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Check for updates + m_menuItemCheckUpdate + none + + + onMenuItemCheckUpdateClick + + + + + 0 + 1 + + wxID_ABOUT + wxITEM_NORMAL + About + m_menuItemAbout + none + + + onMenuItemAboutClick + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_CLOSE + Close + + 0 + + + 0 + + 1 + m_buttonClose + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonCloseClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + Start + + 0 + + + 0 + + 1 + m_buttonStart + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonStartClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/mainwin.py b/subsync/gui/mainwin.py new file mode 100644 index 0000000..063ed50 --- /dev/null +++ b/subsync/gui/mainwin.py @@ -0,0 +1,216 @@ +import gui.mainwin_layout +import wx +from gui.syncwin import SyncWin +from gui.settingswin import SettingsWin +from gui.downloadwin import DownloadWin, UpdateWin +from gui.aboutwin import AboutWin +from gui.errorwin import error_dlg +import assets +import img +import config +import loggercfg +from settings import settings +from error import Error + +import logging +logger = logging.getLogger(__name__) + + +def logRunCmd(sub, ref): + args = [('--sub', '"{}"'.format(sub.path)), + ('--sub-stream', sub.no + 1), + ('--sub-lang', sub.lang), + ('--sub-enc', sub.enc), + ('--ref', '"{}"'.format(ref.path)), + ('--ref-stream', ref.no + 1), + ('--ref-lang', ref.lang), + ('--ref-enc', ref.enc), + ('--ref-fps', ref.fps) ] + + cmd = [ '{}={}'.format(*arg) + for arg in args + if arg[1] != None and arg[1] != '' ] + + logger.info('run command: subsync %s', ' '.join(cmd)) + + +class MainWin(gui.mainwin_layout.MainWin): + def __init__(self, parent, subs=None, refs=None): + gui.mainwin_layout.MainWin.__init__(self, parent) + + img.setWinIcon(self) + self.m_buttonMenu.SetLabel(u'\u2630') + self.m_panelMain.GetSizer().SetSizeHints(self) + + if config.assetupd == None: + self.m_menu.Remove(self.m_menuItemCheckUpdate.GetId()) + + self.m_panelSub.setStream(subs) + self.m_panelSub.stream.types = ('subtitle/text',) + + self.m_panelRef.setStream(refs) + self.m_panelRef.stream.types = ('subtitle/text', 'audio') + + self.m_sliderMaxDist.SetValue(settings().windowSize / 60.0) + self.onSliderMaxDistScroll(None) + + self.Fit() + self.Layout() + + ''' + def reload(self): + subs = self.m_panelSub.stream + refs = self.m_panelRef.stream + self.Unbind(wx.EVT_CLOSE) + self.Close(force=True) + win = MainWin(None, subs, refs) + win.Show() + ''' + + def onSliderMaxDistScroll(self, event): + val = self.m_sliderMaxDist.GetValue() + self.m_textMaxDist.SetLabel(_('{} min').format(val)) + settings().set(windowSize=val * 60.0) + + def onButtonMenuClick(self, event): + self.PopupMenu(self.m_menu) + + def onMenuItemSettingsClick(self, event): + with SettingsWin(self, settings()) as dlg: + if dlg.ShowModal() == wx.ID_OK: + newSettings = dlg.getSettings() + + if settings().logLevel != newSettings.logLevel: + loggercfg.setLevel(newSettings.logLevel) + + if settings().logBlacklist != newSettings.logBlacklist: + loggercfg.setBlacklistFilters(newSettings.logBlacklist) + + if settings() != newSettings: + settings().set(**newSettings.items()) + settings().save() + + def onMenuItemCheckUpdateClick(self, event): + if assets.isUpdateDownloadInProgress(): + if UpdateWin(self, allowHide=True).ShowModal() != wx.ID_OK: + return + + assets.updater.load() + if assets.updater.upgradeReady: + if askForUpdate(self): + assets.updater.upgrade() + self.Unbind(wx.EVT_CLOSE) + self.Close(force=True) + else: + dlg = wx.MessageDialog( + self, + _('There is no upgrade available'), + _('Upgrade'), + wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + + def onMenuItemAboutClick(self, event): + AboutWin(self).ShowModal() + + def onButtonCloseClick(self, event): + self.Close() + + @error_dlg + def onButtonStartClick(self, event): + settings().save() + self.validateSelection() + logRunCmd(self.m_panelSub.stream, self.m_panelRef.stream) + + if self.validateAssets(): + sub = self.m_panelSub.stream + ref = self.m_panelRef.stream + dlg = SyncWin(self, sub, ref) + dlg.ShowModal() + + def validateSelection(self): + subs = self.m_panelSub.stream + refs = self.m_panelRef.stream + if subs.path == None or subs.no == None: + raise Error(_('Subtitles not set')) + if refs.path == None or refs.no == None: + raise Error(_('Reference file not set')) + if subs.path == refs.path and subs.no == refs.no: + raise Error(_('Subtitles can\'t be the same as reference')) + if not (subs.lang == None and refs.lang == None): + if subs.lang == None: + raise Error(_('Select subtitles language first')) + if refs.lang == None: + raise Error(_('Select reference language first')) + + def validateAssets(self): + subs = self.m_panelSub.stream + refs = self.m_panelRef.stream + + needAssets = [] + if refs.type == 'audio': + needAssets.append(dict(type='speech', params=[refs.lang])) + if subs.lang and refs.lang and subs.lang != refs.lang: + needAssets.append(dict(type='dict', params=[subs.lang, refs.lang], + permutable=True)) + + missingAssets = [ a for a in needAssets if assets.getLocalAsset(**a) == None ] + if len(missingAssets) == 0: + return True + + downloadAssets = [] + for id in missingAssets: + asset = assets.assets.getRemoteAsset(**id, raiseIfMissing=True) + downloadAssets.append(asset) + + msg = _('Following assets must be download to continue:\n') + msg += '\n'.join([' - ' + asset['title'] for asset in downloadAssets]) + msg += '\n\n' + _('Download now?') + title = _('Download assets') + with wx.MessageDialog(self, msg, title, wx.YES_NO | wx.ICON_QUESTION) as dlg: + if dlg.ShowModal() == wx.ID_YES: + return self.downloadAssets(downloadAssets) + + def downloadAssets(self, assetsl): + for asset in assetsl: + down = assets.Downloader(**asset) + down.start(name='AssetDown', daemon=False) + title = asset['title'] + + with DownloadWin(self, down, title=title) as dlg: + if dlg.ShowModal() != wx.ID_OK: + return False + return True + + @error_dlg + def onClose(self, event): + if assets.isUpdateDownloadInProgress() and not askForUpdateTermination(self): + UpdateWin(self, allowHide=event.CanVeto()).ShowModal() + event.Veto() + + if settings().askForUpdate and not assets.isUpdateDownloadInProgress(): + assets.updater.load() + if assets.updater.upgradeReady and askForUpdate(self): + assets.updater.upgrade() + event.Veto(False) + + if not event.GetVeto(): + event.Skip() + + +def askForUpdateTermination(parent): + dlg = wx.MessageDialog( + parent, + _('Update is being download, do you want to terminate?'), + _('Upgrade'), + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + return dlg.ShowModal() == wx.ID_YES + + +def askForUpdate(parent): + dlg = wx.MessageDialog( + parent, + _('New version is ready to be installed. Upgrade now?'), + _('Upgrade'), + wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) + return dlg.ShowModal() == wx.ID_YES + diff --git a/subsync/gui/openwin.fbp b/subsync/gui/openwin.fbp new file mode 100644 index 0000000..083493a --- /dev/null +++ b/subsync/gui/openwin.fbp @@ -0,0 +1,1300 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + openwin_layout + 1000 + none + 1 + OpenWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + 440,420 + OpenWin + + 620,420 + wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER + ; forward_declare + Select stream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 3 + 0 + + fgSizer14 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxEXPAND|wxTOP|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Input file: + + 0 + + + 0 + + 1 + m_staticText31 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 0 + + 0 + wxBOTH + 0 + 0 + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxEXPAND|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_textPath + 1 + + + none + 1 + + Resizable + 1 + + wxTE_READONLY + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_FILE_OPEN; wxART_BUTTON + + 1 + 0 + 1 + + 1 + 0 + 0 + + Dock + 0 + Left + 1 + + 1 + + + 0 + 0 + + wxID_OPEN + Open + + 0 + + + 0 + + 1 + m_buttonOpen + 1 + + + protected + 1 + + Resizable + + 1 + + wxBU_AUTODRAW + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonOpenClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxTOP|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Select stream: + + 0 + + + 0 + + 1 + m_staticText311 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_listStreams + 1 + + + protected + 1 + + Resizable + 1 + + wxLC_ALIGN_LEFT|wxLC_REPORT|wxLC_SINGLE_SEL + StreamList; gui.streamlist; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + onListStreamsDClick + + + + + + + + + + + + + + + + + onListStreamsSelect + + + + onListStreamsSelect + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 2 + wxBOTH + 1 + + 0 + + fgSizer20 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Language: + + 0 + + + 0 + + 1 + m_staticText16 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_choiceLang + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ChoiceLang; gui.choicelang; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onChoiceLangChoice + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Encoding: + + 0 + + + 0 + + 1 + m_staticText161 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxALIGN_CENTER_VERTICAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_choiceEncoding + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ChoiceCharEnc; gui.choiceenc; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onChoiceEncChoice + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline8 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_RIGHT|wxBOTTOM + 1 + + 0 + wxBOTH + + + 0 + + fgSizer81 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_CANCEL + Cancel + + 0 + + + 0 + + 1 + m_buttonCancel + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + OK + + 0 + + + 0 + + 1 + m_buttonOk + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/openwin.py b/subsync/gui/openwin.py new file mode 100644 index 0000000..77414fb --- /dev/null +++ b/subsync/gui/openwin.py @@ -0,0 +1,97 @@ +import gui.openwin_layout +import wx +from stream import Stream +from gui.filedlg import showOpenFileDlg +from gui.filedrop import setFileDropTarget +from gui.errorwin import error_dlg +from error import Error +from data.filetypes import subtitleWildcard, videoWildcard + + +@error_dlg +def showOpenSubFileDlg(parent, stream): + props = {} + if stream.path != None: + props['defaultFile'] = stream.path + + props['wildcard'] = '|'.join([ + _('All supported files'), subtitleWildcard + ';' + videoWildcard, + _('Subtitle files'), subtitleWildcard, + _('Video files'), videoWildcard, + _('All files'), '*.*' ]) + + path = showOpenFileDlg(parent, **props) + if path != None: + return Stream(path=path, types=stream.types) + + +class OpenWin(gui.openwin_layout.OpenWin): + def __init__(self, parent, stream): + gui.openwin_layout.OpenWin.__init__(self, parent) + setFileDropTarget(self, self.onDropFile) + self.stream = Stream(stream=stream) + self.openStream(stream) + + @error_dlg + def openStream(self, stream=None, path=None): + if path: + stream = Stream(path=path, types=self.stream.types) + + self.stream = stream + self.m_textPath.SetValue(self.stream.path) + self.m_textPath.SetInsertionPoint(self.m_textPath.GetLastPosition()) + + self.m_choiceEncoding.Enable(False) + self.m_buttonOk.Enable(False) + + lang = stream.lang + enc = stream.enc + + self.m_listStreams.setStreams(stream.streams, stream.types) + + if self.m_listStreams.GetItemCount() == 0: + raise Error(_('There are no usable streams'), + path=stream.path, + types=self.stream.types) + + if stream.no != None: + self.selectStream(stream.stream()) + + self.m_choiceLang.setLang(lang) + self.m_choiceEncoding.setCharEnc(enc) + + def selectStream(self, stream, updateLang=False): + self.m_listStreams.selectStream(stream.no) + self.stream.no = stream.no + self.stream.type = stream.type + + if updateLang and stream.lang: + self.stream.lang = stream.lang + self.m_choiceLang.setLang(stream.lang) + + self.m_choiceEncoding.Enable(stream.type == 'subtitle/text') + self.m_buttonOk.Enable(True) + + def onChoiceLangChoice(self, event): + self.stream.lang = self.m_choiceLang.getLang() + + def onListStreamsSelect(self, event): + index = self.m_listStreams.getSelectedStream() + if index != None: + self.selectStream(self.stream.streams[index], updateLang=True) + + def onListStreamsDClick(self, event): + self.EndModal(wx.ID_OK) + + def onChoiceEncChoice(self, event): + self.stream.enc = self.m_choiceEncoding.getCharEnc() + + @error_dlg + def onButtonOpenClick(self, event): + stream = showOpenSubFileDlg(self, self.stream) + if stream != None and stream.isOpen(): + self.openStream(stream) + + def onDropFile(self, filename): + self.openStream(path=filename) + return True diff --git a/subsync/gui/settingswin.fbp b/subsync/gui/settingswin.fbp new file mode 100644 index 0000000..fe62144 --- /dev/null +++ b/subsync/gui/settingswin.fbp @@ -0,0 +1,3317 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + settingswin_layout + 1000 + none + 1 + SettingsWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + -1,-1 + SettingsWin + + + wxDEFAULT_DIALOG_STYLE + ; forward_declare + Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelMain + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 0 + 0 + + fgSizer3 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxEXPAND|wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_notebook + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + General + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelGeneral + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 2 + wxBOTH + 1 + + 0 + + fgSizer4 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Output filename: + + 0 + + + 0 + + 1 + m_staticText3 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + append language code + + 0 + + + 0 + + 1 + m_appendLangCode + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Updates: + + 0 + + + 0 + + 1 + m_staticText4 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + download updates automatically + + 0 + + + 0 + + 1 + m_autoUpdate + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + ask for update on exit + + 0 + + + 0 + + 1 + m_askForUpdate + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Synchronization + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelSynchro + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 2 + wxBOTH + 1 + + 0 + + fgSizer5 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Max points distance: + + 0 + + + 0 + + 1 + m_staticText3 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0.01 + 2 + 1000 + + 0 + + 0 + + 0 + + 1 + m_maxPointDist + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Min points no: + + 0 + + + 0 + + 1 + m_staticText8 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 20 + 1000 + + 0 + + 0 + + 0 + + 1 + m_minPointsNo + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Min world length (letters): + + 0 + + + 0 + + 1 + m_staticText13 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 5 + 1000 + + 0 + + 0 + + 0 + + 1 + m_minWordLen + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Min worlds similarity: + + 0 + + + 0 + + 1 + m_staticText15 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0.01 + 0.6 + 1 + + 0 + + 0 + + 0 + + 1 + m_minWordsSim + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Min correlation factor: + + 0 + + + 0 + + 1 + m_staticText14 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 1e-08 + 0.9999 + 1 + + 0 + + 0 + + 0 + + 1 + m_minCorrelation + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Min speech recognition score: + + 0 + + + 0 + + 1 + m_staticText9 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0.01 + 0.3 + 1 + + 0 + + 0 + + 0 + + 1 + m_minWordProb + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Extractor jobs no: + + 0 + + + 0 + + 1 + m_staticText10 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + 2 + wxBOTH + 1 + 0 + 0 + + fgSizer9 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + auto + + 0 + + + 0 + + 1 + m_checkAutoJobsNo + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onCheckAutoJobsNoCheck + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + + 1 + + 0 + 0 + wxID_ANY + 4 + 1000 + + 0 + + 1 + + 0 + + 1 + m_jobsNo + 1 + + + protected + 1 + + Resizable + 1 + + wxSP_ARROW_KEYS + ; ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debug + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelDebug + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 4 + 0 + + fgSizer6 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + enable advanced debug options + + 0 + + + 0 + + 1 + m_debugOptions + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxALIGN_CENTER_VERTICAL + 1 + + 2 + wxBOTH + 1 + + 0 + + fgSizer14 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Logging level: + + 0 + + + 0 + + 1 + m_staticText111 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + "all" "debug" "info" "warning" "error" "critical" + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_choiceLogLevel + 1 + + + protected + 1 + + Resizable + 3 + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxRIGHT|wxALIGN_CENTER_VERTICAL + 1 + + 3 + wxBOTH + 1 + 0 + 0 + + fgSizer7 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + log to file + + 0 + + + 0 + + 1 + m_checkLogToFile + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onCheckLogToFileCheck + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxEXPAND|wxTOP|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_textLogFilePath + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_FILE_OPEN; wxART_BUTTON + + 1 + 0 + 1 + + 1 + 0 + 0 + + Dock + 0 + Left + 0 + + 1 + + + 0 + 0 + + wxID_ANY + MyButton + + 0 + + + 0 + + 1 + m_buttonLogFileSelect + 1 + + + protected + 1 + + Resizable + + 1 + + wxBU_AUTODRAW + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonLogFileSelectClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Filter out logs from modules (one per line): + + 0 + + + 0 + + 1 + m_staticText12 + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_textLogBlacklist + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_MULTILINE + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxALIGN_RIGHT + 1 + + 0 + wxBOTH + + + 0 + + fgSizer8 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_DEFAULT + Restore defaults + + 0 + + + 0 + + 1 + m_buttonRestoreDefaults + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonRestoreDefaultsClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_CANCEL + Cancel + + 0 + + + 0 + + 1 + m_buttonCancel + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + OK + + 0 + + + 0 + + 1 + m_buttonOk + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/settingswin.py b/subsync/gui/settingswin.py new file mode 100644 index 0000000..7285c9a --- /dev/null +++ b/subsync/gui/settingswin.py @@ -0,0 +1,94 @@ +import gui.settingswin_layout +from gui.filedlg import showSaveFileDlg +from settings import Settings +import multiprocessing +import wx + + +class SettingsWin(gui.settingswin_layout.SettingsWin): + def __init__(self, parent, settings): + gui.settingswin_layout.SettingsWin.__init__(self, parent) + self.setSettings(settings) + + def setSettings(self, settings): + self.settings = Settings(settings) + + self.m_jobsNo.SetValue(max(multiprocessing.cpu_count(), 2)) + + for field, key, val in self.settingsFieldsGen(): + if val != None: + field.SetValue(val) + + jobsNo = self.settings.jobsNo + self.m_checkAutoJobsNo.SetValue(jobsNo == None) + self.m_jobsNo.Enable(jobsNo != None) + + logLevel = self.settings.logLevel / 10 + if logLevel >= 0 and logLevel < self.m_choiceLogLevel.GetCount(): + self.m_choiceLogLevel.SetSelection(logLevel) + + logFile = self.settings.logFile + self.m_checkLogToFile.SetValue(logFile != None) + self.m_textLogFilePath.Enable(logFile != None) + self.m_buttonLogFileSelect.Enable(logFile != None) + self.m_textLogFilePath.SetValue(logFile if logFile else '') + + logBlacklist = self.settings.logBlacklist + if logBlacklist == None: + logBlacklist = [] + self.m_textLogBlacklist.SetValue('\n'.join(logBlacklist)) + + def getSettings(self): + for field, key, val in self.settingsFieldsGen(): + setattr(self.settings, key, field.GetValue()) + + if self.m_checkAutoJobsNo.IsChecked(): + self.settings.jobsNo = None + + logLevel = self.m_choiceLogLevel.GetSelection() + if logLevel != wx.NOT_FOUND: + self.settings.logLevel = logLevel * 10 + + if self.m_checkLogToFile.IsChecked(): + self.settings.logFile = self.m_textLogFilePath.GetValue() + else: + self.settings.logFile = None + + logBlacklist = self.m_textLogBlacklist.GetValue().split() + if len(logBlacklist) > 0: + self.settings.logBlacklist = logBlacklist + else: + self.settings.logBlacklist = None + + return self.settings + + def settingsFieldsGen(self): + for key, val in self.settings.items().items(): + field = 'm_' + key + if hasattr(self, field): + yield getattr(self, field), key, val + + def onCheckAutoJobsNoCheck(self, event): + auto = self.m_checkAutoJobsNo.IsChecked() + self.m_jobsNo.Enable(not auto) + + def onCheckLogToFileCheck(self, event): + enabled = self.m_checkLogToFile.IsChecked() + self.m_textLogFilePath.Enable(enabled) + self.m_buttonLogFileSelect.Enable(enabled) + + def onButtonLogFileSelectClick(self, event): + path = showSaveFileDlg(self) + if path != None: + self.m_textLogFilePath.SetValue(path) + + def onButtonRestoreDefaultsClick(self, event): + dlg = wx.MessageDialog( + self, + _('Are you sure you want to reset settings to defaults?'), + _('Restore defaults'), + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + + if dlg.ShowModal() == wx.ID_YES: + self.setSettings(Settings()) + diff --git a/subsync/gui/streamlist.py b/subsync/gui/streamlist.py new file mode 100644 index 0000000..37f5999 --- /dev/null +++ b/subsync/gui/streamlist.py @@ -0,0 +1,42 @@ +import wx + + +class StreamList(wx.ListCtrl): + def __init__(self, *args, **kwargs): + wx.ListBox.__init__(self, *args, **kwargs) + self.InsertColumn(0, _('id'), format=wx.LIST_FORMAT_RIGHT, width=40) + self.InsertColumn(1, _('language'), width=80) + self.InsertColumn(2, _('type'), width=120) + self.InsertColumn(3, _('description'), width=320) + + def setStreams(self, streams, types=None): + self.DeleteAllItems() + + row = 0 + for no in sorted(streams): + s = streams[no] + if types == None or s.type in types: + lang = s.lang if s.lang else '' + + self.InsertItem(row, str(s.no + 1)) + self.SetItem(row, 1, lang) + self.SetItem(row, 2, s.type) + self.SetItem(row, 3, s.title) + self.SetItemData(row, s.no) + row += 1 + + def selectStream(self, no): + sel = self.GetFirstSelected() + if sel != -1 and no == self.GetItemData(sel): + return True + + for sel in range(self.GetItemCount()): + if no == self.GetItemData(sel): + self.Select(sel) + return True + + return False + + def getSelectedStream(self): + sel = self.GetFirstSelected() + return self.GetItemData(sel) if sel != -1 else None diff --git a/subsync/gui/subpanel.fbp b/subsync/gui/subpanel.fbp new file mode 100644 index 0000000..c2c1af8 --- /dev/null +++ b/subsync/gui/subpanel.fbp @@ -0,0 +1,368 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + subpanel_layout + 1000 + none + 1 + SubtitlePanel + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + + 1 + 1 + impl_virtual + + + 0 + wxID_ANY + + + SubtitlePanel + + -1,-1 + ; forward_declare + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + wxBOTH + 0 + 0 + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + 380,-1 + 1 + m_textSubPath + 1 + + + none + 1 + + Resizable + 1 + + wxTE_READONLY + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxBOTTOM + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_choiceSubLang + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ChoiceLang; gui.choicelang; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onChoiceSubLang + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_FILE_OPEN; wxART_BUTTON + + 1 + 0 + 1 + + 1 + 0 + 0 + + Dock + 0 + Left + 1 + + 1 + + + 0 + 0 + + wxID_OPEN + MyButton + + 0 + + + 0 + + 1 + m_buttonSubOpen + 1 + + + protected + 1 + + Resizable + + 1 + + wxBU_AUTODRAW + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonSubOpenClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/subpanel.py b/subsync/gui/subpanel.py new file mode 100644 index 0000000..d30515e --- /dev/null +++ b/subsync/gui/subpanel.py @@ -0,0 +1,50 @@ +import gui.subpanel_layout +import wx +import os +from stream import Stream +from settings import settings +from gui.openwin import OpenWin, showOpenSubFileDlg +from gui.filedrop import setFileDropTarget +from gui.errorwin import error_dlg + + +class SubtitlePanel(gui.subpanel_layout.SubtitlePanel): + def __init__(self, parent, *args, **kwargs): + wx.FileDropTarget.__init__(self) + gui.subpanel_layout.SubtitlePanel.__init__(self, parent) + setFileDropTarget(self, self.onDropSubFile) + self.stream = Stream() + + @error_dlg + def onButtonSubOpenClick(self, event): + stream = self.stream + if not stream.isOpen(): + stream = showOpenSubFileDlg(self, self.stream) + self.showStreamSelectDlg(stream) + + def showStreamSelectDlg(self, stream): + if stream != None and stream.isOpen(): + with OpenWin(self, stream) as dlg: + if dlg.ShowModal() == wx.ID_OK and dlg.stream.isOpen(): + self.setStream(dlg.stream) + + def onChoiceSubLang(self, event): + self.stream.lang = self.m_choiceSubLang.getLang() + + def onDropSubFile(self, filename): + self.openStreamSelectDlg(filename) + return True + + @error_dlg + def openStreamSelectDlg(self, filename): + stream = Stream(path=filename, types=self.stream.types) + settings().lastdir = os.path.dirname(filename) + self.showStreamSelectDlg(stream) + + def setStream(self, stream): + if stream != None and stream.isOpen(): + self.stream.assign(stream) + self.m_textSubPath.SetValue('{}:{}'.format(self.stream.path, self.stream.no + 1)) + self.m_textSubPath.SetInsertionPoint(self.m_textSubPath.GetLastPosition()) + self.m_choiceSubLang.setLang(self.stream.lang) + diff --git a/subsync/gui/syncwin.fbp b/subsync/gui/syncwin.fbp new file mode 100644 index 0000000..b502a9e --- /dev/null +++ b/subsync/gui/syncwin.fbp @@ -0,0 +1,2564 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + syncwin_layout + 1000 + none + 1 + SyncWin + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + SyncWin + + + wxDEFAULT_DIALOG_STYLE + ; forward_declare + Synchronization + + + + + + + + + + + + + + onClose + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel1 + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 1 + wxBOTH + 0 + 4 + 0 + + fgSizer6 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Synchronizing... + + 0 + + + 0 + + 1 + m_textStatus + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + wxEXPAND|wxALIGN_CENTER_VERTICAL|wxBOTTOM + 1 + + 2 + wxBOTH + 0 + 0 + 0 + + fgSizer7 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + 280,-1 + 1 + m_gaugeProgress + 1 + + + protected + 1 + + 100 + Resizable + 1 + + wxGA_HORIZONTAL + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0 % + + 0 + + + 0 + + 1 + m_textProgress + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelError + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 2 + wxBOTH + 1 + 0 + 0 + + fgSizer82 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_WARNING; wxART_CMN_DIALOG + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmapErrorIcon + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxEXPAND + 1 + + 1 + wxBOTH + 0 + 0,1 + 0 + + fgSizer83 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALIGN_CENTER_VERTICAL|wxTOP|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_textErrorMsg + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + wxSYS_COLOUR_HIGHLIGHT + 1 + + 0 + 0 + wxID_ANY + [details] + + 0 + + + 0 + + 1 + m_textErrorDetails + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + onTextErrorDetailsClick + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxTOP + 1 + + 0 + wxBOTH + 2 + 0 + 0 + + fgSizer8 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxEXPAND|wxTOP|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_TICK_MARK; wxART_MENU + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmapTick + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxTOP|wxBOTTOM|wxLEFT + 0 + + 1 + 1 + 1 + 1 + + + + + + + Load From Art Provider; wxART_CROSS_MARK; wxART_MENU + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmapCross + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Synchronization: 0 points + + 0 + + + 0 + + 1 + m_textSync + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + wxSYS_COLOUR_HIGHLIGHT + 1 + + 0 + 0 + wxID_ANY + [show more] + + 0 + + + 0 + + 1 + m_textShowDetails + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + onTextShowDetailsClick + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxRIGHT|wxLEFT + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelDetails + 1 + + + protected + 1 + + Resizable + 1 + + ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + + + + + + + + + + + + + + + + + + + + + + 2 + wxBOTH + 0 + 0 + 0 + + fgSizer61 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxRIGHT|wxLEFT + 1 + + 2 + wxBOTH + 0,1 + 0 + 0 + + fgSizer16 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxTOP|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + elapsed time: + + 0 + + + 0 + + 1 + m_textElapsedTimeTitle + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxTOP|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0:00 + + 0 + + + 0 + + 1 + m_textElapsedTime + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + correlation: + + 0 + + + 0 + + 1 + m_textCorrelationTitle + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + 0.00 % + + 0 + + + 0 + + 1 + m_textCorrelation + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + formula: + + 0 + + + 0 + + 1 + m_textFormulaTitle + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + - + + 0 + + + 0 + + 1 + m_textFormula + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + max change: + + 0 + + + 0 + + 1 + m_textMaxChangeTitle + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + - + + 0 + + + 0 + + 1 + m_textMaxChange + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_BOTTOM|wxLEFT|wxRIGHT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + wxSYS_COLOUR_HIGHLIGHT + 1 + + 0 + 0 + wxID_ANY + [hide] + + 0 + + + 0 + + 1 + m_textHideDetails + 1 + + + protected + 1 + + Resizable + 1 + + wxALIGN_RIGHT + ; forward_declare + 0 + + + + + -1 + + + + + + + + + + onTextHideDetailsClick + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline1 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; forward_declare + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_RIGHT|wxBOTTOM + 1 + + 0 + wxBOTH + + + 0 + + fgSizer81 + wxFLEX_GROWMODE_SPECIFIED + none + 1 + 0 + + 5 + wxALL|wxEXPAND|wxALIGN_CENTER_VERTICAL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_ANY + Debug + + 0 + + + 0 + + 1 + m_buttonDebugMenu + 1 + + + protected + 1 + + Resizable + 1 + + wxBU_EXACTFIT + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonDebugMenuClick + + + + + + + + + + + + + + + + + + + + + + + + + Debug menu + m_menuDebug + protected + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Enable save button + m_menuItemEnableSave + none + + + onMenuItemEnableSaveClick + + + + m_separator1 + none + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Save pipeline graph + m_menuItemSaveGraph + none + + + onMenuItemSaveGraphClick + + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Show pipeline graph + m_menuItemShowGraph + none + + + onMenuItemShowGraphClick + + + + m_separator2 + none + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Dump subtitle words + m_menuItemDumpSubWords + none + + + onMenuItemDumpSubWordsClick + + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Dump reference words + m_menuItemDumpRefWords + none + + + onMenuItemDumpRefWordsClick + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 1 + wxID_CANCEL + Close + + 0 + + + 0 + + 1 + m_buttonClose + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_STOP + Stop + + 0 + + + 0 + + 1 + m_buttonStop + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonStopClick + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 1 + 0 + Dock + 0 + Left + 0 + + 1 + + 0 + 0 + wxID_SAVE + Save + + 0 + + + 0 + + 1 + m_buttonSave + 1 + + + protected + 1 + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onButtonSaveClick + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subsync/gui/syncwin.py b/subsync/gui/syncwin.py new file mode 100644 index 0000000..b72b26d --- /dev/null +++ b/subsync/gui/syncwin.py @@ -0,0 +1,287 @@ +import gui.syncwin_layout +import wx +import synchro +import gui.filedlg +import gui.errorwin +import thread +import data.filetypes +from subtitle import Subtitles, makeTimestamp +from settings import settings +import img +import error +import pysubs2.exceptions +import time +import os +import collections + + +class SyncWin(gui.syncwin_layout.SyncWin): + def __init__(self, parent, subs, refs): + gui.syncwin_layout.SyncWin.__init__(self, parent) + + self.m_buttonDebugMenu.SetLabel(u'\u2630') + img.setItemBitmap(self.m_bitmapTick, 'tickmark') + img.setItemBitmap(self.m_bitmapCross, 'crossmark') + + if settings().debugOptions: + import graph + graph.init() + self.m_buttonDebugMenu.Show() + + self.m_buttonStop.SetFocus() + self.Fit() + self.Layout() + + self.subs = subs + self.refs = refs + + self.isRunning = False + self.isCorrelated = False + self.isSubReady = False + + self.errors = collections.OrderedDict() + + self.updateTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.onUpdateTimerTick, self.updateTimer) + + # workaround for GUI freeze + wx.CallAfter(self.init) + + @gui.errorwin.error_dlg + def init(self): + self.startTime = time.time() + + self.sync = synchro.Synchronizer(self, self.subs, self.refs) + self.sync.start() + self.stats = self.sync.getStats() + + self.isRunning = True + self.updateTimer.Start(500) + + def onUpdateTimerTick(self, event): + if self.isRunning: + stats = self.sync.getStats() + elapsed = time.time() - self.startTime + maxChange = self.sync.getMaxChange() + + self.m_textSync.SetLabel(_('Synchronization: {} points').format(stats.points)) + self.m_textElapsedTime.SetLabel(makeTimestamp(elapsed, short=True, fraction=False)) + self.m_textCorrelation.SetLabel('{:.2f} %'.format(100.0 * stats.factor)) + self.m_textFormula.SetLabel(str(stats.formula)) + self.m_textMaxChange.SetLabel(makeTimestamp(maxChange, short=True)) + + if not self.isCorrelated and stats.correlated: + self.isCorrelated = stats.correlated + self.m_bitmapCross.Hide() + self.m_bitmapTick.Show() + + if self.isSubReady: + self.m_buttonSave.Enable() + + self.Layout() + + if self.sync.isRunning(): + self.setProgress(self.sync.getProgress()) + else: + self.stop() + self.setProgress(1.0) + + @thread.gui_thread + def onSubReady(self): + self.isSubReady = True + if self.isCorrelated: + self.m_buttonSave.Enable() + + @thread.gui_thread_cnt('pendingErrorsNo') + def onError(self, source, err): + msg = errorToString(source, err) + self.addError(source, err, msg, self.pendingErrorsNo.get() <= 0) + + def addError(self, source, err, msg, update=True): + if len(self.errors) == 0: + self.m_panelError.Show() + + if msg not in self.errors: + self.errors[msg] = error.ErrorsGroup(msg) + self.errors[msg].add(err) + + if update: + msgs = [ err.message for err in self.errors.values() ] + self.m_textErrorMsg.SetLabelText('\n'.join(msgs)) + + self.Fit() + self.Layout() + + def setProgress(self, progress): + if progress != None: + pr = int(progress * 100) + self.m_gaugeProgress.SetValue(pr) + self.m_textProgress.SetLabel(' {:3} % '.format(pr)) + else: + self.m_gaugeProgress.Pulse() + + def stop(self): + self.m_buttonStop.Enable(False) + self.m_buttonStop.Show(False) + self.m_buttonClose.Enable(True) + self.m_buttonClose.Show(True) + + if self.isRunning: + self.isRunning = False + self.updateTimer.Stop() + self.sync.stop() + + if self.isCorrelated and self.isSubReady: + self.m_buttonSave.Enable() + self.m_bitmapTick.Show() + self.m_bitmapCross.Hide() + self.m_textStatus.SetLabel(_('Subtitles synchronized')) + else: + self.m_bitmapTick.Hide() + self.m_bitmapCross.Show() + if self.isSubReady: + self.m_textStatus.SetLabel(_('Couldn\'t synchronize')) + else: + self.m_textStatus.SetLabel(_('Subtitles not ready')) + + self.m_buttonClose.SetFocus() + self.m_buttonSave.SetFocus() + self.Fit() + self.Layout() + + def onClose(self, event): + self.stop() + event.Skip() + + def onButtonStopClick(self, event): + if self.isRunning: + self.stop() + else: + self.Close() + + @gui.errorwin.error_dlg + def onButtonSaveClick(self, event): + path = self.saveFileDlg(self.refs.path) + if path != None: + fps = self.subs.fps if self.subs.fps != None else self.refs.fps + try: + self.sync.getSynchronizedSubtitles().save(path, fps=fps) + except pysubs2.exceptions.Pysubs2Error as err: + gui.errorwin.showExceptionDlg(self) + + def onTextShowDetailsClick(self, event): + self.m_panelDetails.Show() + self.m_textShowDetails.Hide() + self.Fit() + self.Layout() + + def onTextHideDetailsClick(self, event): + self.m_panelDetails.Hide() + self.m_textShowDetails.Show() + self.Fit() + self.Layout() + + def onTextErrorDetailsClick(self, event): + msgs = [] + for err in self.errors.values(): + msgs.append(err.message) + msgs += list(err.descriptions) + items = sorted(err.fields.items()) + msgs += [ '{}: {}'.format(k, error.formatFieldsVals(v, 10)) for k, v in items ] + msgs.append('') + showDetailsWin(self, '\n'.join(msgs), _('Error')) + + def saveFileDlg(self, path=None, suffix=None): + props = {} + filters = '|'.join('|'.join(x) for x in data.filetypes.subtitleTypes) + props['wildcard'] = '{}|{}|*.*'.format(filters, _('All files')) + props['defaultFile'] = self.genDefaultFileName(path, suffix) + if path: + props['defaultDir'] = os.path.dirname(path) + return gui.filedlg.showSaveFileDlg(self, **props) + + def genDefaultFileName(self, path, suffix=None): + try: + res = [] + basename, _ = os.path.splitext(os.path.basename(path)) + res.append(basename) + + if suffix: + res.append(suffix) + + if settings().appendLangCode and self.subs.lang: + res.append(self.subs.lang) + + res.append('srt') + return '.'.join(res) + except Exception as e: + logger.warning('%r', e) + + def onButtonDebugMenuClick(self, event): + self.PopupMenu(self.m_menuDebug) + + def onMenuItemEnableSaveClick(self, event): + self.m_buttonSave.Enable() + + @gui.errorwin.error_dlg + def onMenuItemSaveGraphClick(self, event): + import graph + wildcard = 'Graphviz DOT|*.dot|PNG|*.png|GIF|*.gif|PostScript|*.ps|All files|*.*' + path = gui.filedlg.showSaveFileDlg(self, wildcard=wildcard) + if path != None: + graph.saveGraph(path, [ p.demux for p in self.sync.pipelines ]) + + @gui.errorwin.error_dlg + def onMenuItemShowGraphClick(self, event): + import graph + import config + path = os.path.join(config.assetdir, 'graph.png') + graph.saveGraph(path, [ p.demux for p in self.sync.pipelines ]) + wx.LaunchDefaultApplication(path) + + @gui.errorwin.error_dlg + def onMenuItemDumpSubWordsClick(self, event): + self.saveWordsDlg(self.subs.path, self.sync.correlator.getSubs()) + + @gui.errorwin.error_dlg + def onMenuItemDumpRefWordsClick(self, event): + self.saveWordsDlg(self.refs.path, self.sync.correlator.getRefs()) + + def saveWordsDlg(self, path, words): + subs = Subtitles() + for time, text in words: + subs.add(time, time, text) + + path = self.saveFileDlg(path, suffix='words') + if path != None: + fps = self.subs.fps if self.subs.fps != None else self.refs.fps + subs.save(path, fps=fps) + + +def errorToString(source, err): + if source == 'sub': + if err.fields['module'].startswith('SubtitleDec.decode'): + return _('Some subtitles can\'t be decoded (invalid encoding?)') + elif 'terminated' in err.fields: + return _('Subtitles read failed') + else: + return _('Error during subtitles read') + elif source == 'ref': + if err.fields['module'].startswith('SubtitleDec.decode'): + return _('Some reference subtitles can\'t be decoded (invalid encoding?)') + elif 'terminated' in err.fields: + return _('Reference read failed') + else: + return _('Error during reference read') + else: + return _('Unexpected error occurred') + + +def showDetailsWin(parent, msg, title): + dlg = wx.lib.dialogs.ScrolledMessageDialog(parent, msg, title, + size=(800, 500), style=wx.DEFAULT_FRAME_STYLE) + font = wx.Font(wx.NORMAL_FONT.GetPointSize(), wx.FONTFAMILY_TELETYPE, + wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) + dlg.SetFont(font) + dlg.ShowModal() + diff --git a/subsync/img.py b/subsync/img.py new file mode 100644 index 0000000..de0962b --- /dev/null +++ b/subsync/img.py @@ -0,0 +1,55 @@ +import wx +import os +import config +import logging +logger = logging.getLogger(__name__) + + +_images = {} +_imgdir = '' + + +def init(): + global _imgdir + for d in config.imgdirs: + if os.path.isdir(d): + _imgdir = d + break + + if not _imgdir: + logger.warning('cannot find images directory, tried %s', str(config.imgdirs)) + +init() + + +def getBitmapPath(name): + path = os.path.join(_imgdir, name + '.png') + if os.path.isfile(path): + return path + + logger.warning('cannot load image %s', name) + return None + + +def getBitmap(name): + if name not in _images: + path = getBitmapPath(name) + if path != None: + _images[name] = wx.Bitmap(path) + else: + _images[name] = None + return _images[name] + + +def setItemBitmap(item, name, *args, **kwargs): + img = getBitmap(name) + if img != None: + item.SetBitmap(img, *args, **kwargs) + +def setWinIcon(win): + try: + path = os.path.join(_imgdir, 'icon.png') + icon = wx.Icon(wx.Bitmap(path, wx.BITMAP_TYPE_ANY)) + win.SetIcon(icon) + except Exception as e: + logger.warning('cannot set icon, %r', e) diff --git a/subsync/img/crossmark.png b/subsync/img/crossmark.png new file mode 100644 index 0000000000000000000000000000000000000000..30c37c9f4d190da19761c2a8ba4ab8c1e6ace5a0 GIT binary patch literal 476 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

-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 + +image/svg+xml + diff --git a/subsync/img/logo.png b/subsync/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ecc2e6dcfda435e2093bebf1f92ccf3d379db5ed GIT binary patch literal 2824 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 literal 0 HcmV?d00001 diff --git a/subsync/key.pub b/subsync/key.pub new file mode 100644 index 0000000..9ba2ae2 --- /dev/null +++ b/subsync/key.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA09bU3CgZVvW5AIqLqcAK +orWn3lnQY/8s48SQ/yIhOr36FwQFulIrCnJh2Ikpu9r5qdMOrz3zBGkxZPtJ3vzP +XexgngTVhdldaiZlpQJmKINwOHQoo+WF/tpaAls8sGE0f/UwbVqrlVK+CuL+ZXbY +dp+aH9K/gAEaQfTfMBh6AomNkNDopVuxC9cWQ8yMSpeYzvPfymvZZCCahHxmhE6e +YwHNjnUTWPdQhtgrq1XF2CiOG8zR/roHLrne0BfBp/zQ7wLGHV5cyvP40wvHZmcO +RG4Ynrnv+MWSoFmHlG/AMI41nuw/O+H2BR4ZhDPljd8rwyR/d8Hj5q9GwSB2mg6k +pn2Nsvx2dXmcYAoNfVcVaIoJax+MDa3047Q8heMpiN8zzATRM4ClGylDMXi+aBOp +jWvzqr6iYBJdUe8WeSeA7/t50736CL5ECfuklgJ1lJYHSVSILy/IZIuTKHndO5yN +pYhzaCMj2g9gIrb7w/sOMqAeU85xU+hGNw93tsbptMCGMUsVoqO2EGerTU5fWZsz +qT6S0Y2zUerOawbJ7YsRNFFm4s5vj9IcS4ZQDV10fevimun6dWkK7xKr3EYbLBUt +ZvtXrK++gRMg9/SkXTSMWYnqn/vEEUekOq8Ze7Jnop8PvRPG6lEpnyezs79A3PvI +yYJZjS6dkWDBTMvQR9FYdRkCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/subsync/loggercfg.py b/subsync/loggercfg.py new file mode 100644 index 0000000..891cfcc --- /dev/null +++ b/subsync/loggercfg.py @@ -0,0 +1,55 @@ +import logging +import gizmo + + +class BlacklistFilter(logging.Filter): + def __init__(self, names): + self.blacklist = set(names) + + def filter(self, record): + return record.name not in self.blacklist + + +_activeFilter = None + + +def init(level=None, path=None): + logging.basicConfig( + format='%(relativeCreated)8d:%(threadName)12.12s:%(levelname)8.8s:%(name)12.12s: %(message)s', + level=level, + filename=path) + + def print_log(level, m, msg): + logging.getLogger(m).log(level, msg.strip().replace('\n', '; ')) + + gizmo.setLoggerCallback(print_log) + gizmo.setDebugLevel(level) + + +def terminate(): + gizmo.setLoggerCallback(None) + + +def setLevel(level): + logger = logging.getLogger() + logger.setLevel(level) + for handler in logger.handlers: + handler.setLevel(level) + gizmo.setDebugLevel(level) + + +def setBlacklistFilters(filters): + global _activeFilter + + if _activeFilter: + for handler in logging.root.handlers: + handler.removeFilter(_activeFilter) + + _activeFilter = None + + if filters: + _activeFilter = BlacklistFilter(filters) + + for handler in logging.root.handlers: + handler.addFilter(_activeFilter) + diff --git a/subsync/pipeline.py b/subsync/pipeline.py new file mode 100644 index 0000000..071eb8a --- /dev/null +++ b/subsync/pipeline.py @@ -0,0 +1,143 @@ +import gizmo +import speech +from settings import settings +from error import Error +import math + +import logging +logger = logging.getLogger(__name__) + + +class BasePipeline(object): + def __init__(self, stream): + self.demux = gizmo.Demux(stream.path) + self.extractor = gizmo.Extractor(self.demux) + + self.duration = self.demux.getDuration() + self.timeWindow = [0, self.duration] + + def selectTimeWindow(self, *window): + self.timeWindow = window + self.extractor.selectTimeWindow(*window) + + def start(self): + self.extractor.start() + + def stop(self): + self.extractor.stop() + + def isRunning(self): + return self.extractor.isRunning() + + def getProgress(self): + if self.duration: + pos = self.demux.getPosition() + if math.isclose(pos, 0.0): + pos = self.timeWindow[0] + return (pos - self.timeWindow[0]) / (self.timeWindow[1] - self.timeWindow[0]) + + def connectEosCallback(self, cb, dst=None): + self.extractor.connectEosCallback(cb) + + def connectErrorCallback(self, cb, dst=None): + self.extractor.connectErrorCallback(cb) + + +class SubtitlePipeline(BasePipeline): + def __init__(self, stream): + ''' Speech recognition pipeline: + + Demux --> SpeechDec --[words]--> ... + ''' + + super().__init__(stream) + self.dec = gizmo.SubtitleDec(self.demux, stream.no) + self.dec.setMinWordLen(settings().minWordLen) + if stream.enc != None: + self.dec.setEncoding(stream.enc) + + self.demux.connectDec(self.dec, stream.no) + + def connectWordsCallback(self, cb, dst=None): + self.dec.connectWordsCallback(cb, dst) + + def connectSubsCallback(self, cb, dst=None): + self.dec.connectSubsCallback(cb, dst) + + +class SpeechPipeline(BasePipeline): + def __init__(self, stream): + ''' Speech recognition pipeline: + + Demux --> AudioDec --[inputAudioFormat]--> AudioResampler --> + --> AudioDemux --[speechAudioFormat]--> SpeechRecognition --[words]--> ... + ''' + + super().__init__(stream) + + speechModel = speech.loadSpeechModel(stream.lang) + self.dec = gizmo.AudioDec(self.demux, stream.no) + + inputAudioFormat = self.dec.getFormat() + speechAudioFormat = speech.getSpeechAudioFormat(speechModel, inputAudioFormat) + logger.info('audio format conversion %r to %r', inputAudioFormat, speechAudioFormat) + + self.speechRec = speech.createSpeechRec(speechModel) + self.speechRec.setMinWordProb(settings().minWordProb) + self.speechRec.setMinWordLen(settings().minWordLen) + + self.audioDemux = gizmo.AudioDemux() + self.audioDemux.setOutputFormat(speechAudioFormat.getSampleSize(), + speechAudioFormat.channelsNo) + + if inputAudioFormat.channelsNo > 1: + mixMap = speech.MixMap(inputAudioFormat.channelsNo) + centerNo = speech.MixMap.getChannelNoByName('front center', inputAudioFormat) + if centerNo != None: + mixMap.setPath(centerNo, 0) + else: + mixMap.mixAll(0) + else: + mixMap = speech.MixMap() + + logger.debug('audio channels mixer map %r', mixMap) + self.resampler = gizmo.AudioResampler() + self.resampler.setParams(inputAudioFormat, speechAudioFormat, mixMap.map) + + self.demux.connectDec(self.dec, stream.no) + self.dec.connectOutput(self.resampler) + self.resampler.connectOutput(self.audioDemux) + self.audioDemux.connectOutputChannel(0, self.speechRec) + + def connectWordsCallback(self, cb, dst=None): + self.speechRec.connectWordsCallback(cb, dst) + + +def createProducerPipeline(stream): + if stream.type == 'subtitle/text': + return SubtitlePipeline(stream) + elif stream.type == 'audio': + return SpeechPipeline(stream) + else: + raise Error(_('Not supported stream type'), type=stream.type) + + +def createProducerPipelines(stream, no): + pipes = [] + for i in range(no): + p = createProducerPipeline(stream) + pipes.append(p) + + if p.duration: + partTime = p.duration / no + begin = i * partTime + end = begin + partTime + logger.info('job %i/%i time window set to %.2f - %.2f', i+1, no, begin, end) + p.selectTimeWindow(begin, end) + + else: + logger.warn('cannot get duration - using single pipeline') + break + + return pipes + diff --git a/subsync/pubkey.py b/subsync/pubkey.py new file mode 100644 index 0000000..7d39dc7 --- /dev/null +++ b/subsync/pubkey.py @@ -0,0 +1,24 @@ +import config +from error import Error +import os +from Crypto.PublicKey import RSA +from Crypto.Signature import PKCS1_v1_5 + +_verifier = None + + +def getPublicKey(): + for path in config.keypaths: + if os.path.isfile(path): + with open(path, 'rb') as fp: + return RSA.importKey(fp.read()) + raise Error(_('Cannot load public key'), paths=config.keypaths) + + +def getVerifier(): + global _verifier + if not _verifier: + key = getPublicKey() + _verifier = PKCS1_v1_5.new(key) + return _verifier + diff --git a/subsync/settings.py b/subsync/settings.py new file mode 100644 index 0000000..fbfca20 --- /dev/null +++ b/subsync/settings.py @@ -0,0 +1,79 @@ +import config +import os +import json +from error import Error + +import logging +logger = logging.getLogger(__name__) + + +class Settings(object): + def __init__(self, settings=None, **kw): + self.maxPointDist = 2.0 + self.minPointsNo = 20 + self.appendLangCode = True + self.windowSize = 30.0 * 60.0 + self.minWordProb = 0.3 + self.jobsNo = None + self.minWordLen = 5 + self.minCorrelation = 0.9999 + self.minWordsSim = 0.6 + self.lastdir = '' + self.autoUpdate = True + self.askForUpdate = True + self.debugOptions = False + self.logLevel = logging.WARNING + self.logFile = None + self.logBlacklist = None + + if settings: + self.set(**settings.items()) + self.set(**kw) + + def __eq__(self, other): + for key in self.__dict__.keys() | other.__dict__.keys(): + if not hasattr(self, key) or not hasattr(other, key): + return False + if getattr(self, key) != getattr(other, key): + return False + return True + + def set(self, **state): + for key, val in state.items(): + if hasattr(self, key): + setattr(self, key, val) + else: + logger.warning('invalid entry: %s = %s (%s)', + key, str(val), type(val).__name__) + + def items(self): + return {k: v for k, v in self.__dict__.items()} + + def load(self): + try: + if os.path.isfile(config.configpath): + with open(config.configpath, encoding='utf8') as fp: + cfg = json.load(fp) + logger.info('configuration loaded from %s', config.configpath) + logger.debug('configuration: %r', cfg) + self.set(**cfg) + except Exception as err: + raise Error(_('Cannot load settings file, {}').format(err), path=config.configpath) + + def save(self): + try: + os.makedirs(os.path.dirname(config.configpath), exist_ok=True) + with open(config.configpath, 'w', encoding='utf8') as fp: + json.dump(self.items(), fp, indent=4) + logger.info('configuration saved to %s', config.configpath) + logger.debug('configuration: %r', self.items()) + except Exception as e: + logger.warning('cannot save configuration to %s: %r', config.configpaths, e) + + +_settings = Settings() + + +def settings(): + return _settings + diff --git a/subsync/setup.py b/subsync/setup.py new file mode 100644 index 0000000..58a01f8 --- /dev/null +++ b/subsync/setup.py @@ -0,0 +1,71 @@ +from cx_Freeze import setup, Executable +import sys +import os +import glob +import subprocess +import version + + +build_exe = { + 'packages': ['idna'], + 'excludes': ['updatelangs', 'tkinter', 'tcl', 'ttk'], + 'include_files': ['img', 'key.pub'], +} + +if sys.platform == 'win32': + build_exe['include_files'] += glob.glob(os.path.join(os.path.realpath('..'), '*.dll')) + build_exe['include_msvcr'] = True + + target_base = 'Win32GUI' + target_suffix = '.exe' + +else: + target_base = None + target_suffix = '' + +bdist_msi = { + 'upgrade_code': '{30cde9a7-1f56-49ef-8701-4d6bf8dee50a}', + 'data': { + 'Shortcut': [ + ('StartMenuShortcut', # Shortcut + 'StartMenuFolder', # Directory + 'Subtitle Speech Synchronizer', # Name + 'TARGETDIR', # Component + '[TARGETDIR]subsync.exe', # Target + None, # Arguments + None, # Description + None, # Hotkey + None, # Icon + None, # IconIndex + None, # ShowCmd + 'TARGETDIR' # WkDir + ), + ], + }, +} + +executables = [ + Executable( + '__main__.py', + base = 'Console', + targetName = 'subsync-dbg' + target_suffix, + icon = os.path.join('img', 'icon.ico'), + ), + Executable( + '__main__.py', + base = target_base, + targetName = 'subsync' + target_suffix, + icon = os.path.join('img', 'icon.ico'), + ) +] + +setup( + name = 'subsync', + description = 'Subtitle Speech Synchronizer', + version = version.version_short, + options = { + 'build_exe': build_exe, + 'bdist_msi': bdist_msi, + }, + executables = executables, +) diff --git a/subsync/speech.py b/subsync/speech.py new file mode 100644 index 0000000..80eb5ef --- /dev/null +++ b/subsync/speech.py @@ -0,0 +1,98 @@ +import gizmo +from assets import assets +from error import Error +import json +import os + +import logging +logger = logging.getLogger(__name__) + + +_speechModels = {} + +def loadSpeechModel(lang): + if lang in _speechModels: + return _speechModels[lang] + + logger.info('loading speech recognition model for language %s', lang) + + path = assets.getLocalAsset('speech', [lang], raiseIfMissing=True) + with open(path, encoding='utf8') as fp: + model = json.load(fp) + + # fix paths + if 'sphinx' in model: + dirname = os.path.abspath(os.path.dirname(path)) + sphinx = model['sphinx'] + for key, val in sphinx.items(): + if val.startswith('./'): + sphinx[key] = os.path.join(dirname, *val.split('/')[1:]) + + logger.debug('model ready: %s', model) + _speechModels[lang] = model + return model + + +def createSpeechRec(model): + speechRec = gizmo.SpeechRecognition() + if 'sphinx' in model: + for key, val in model['sphinx'].items(): + speechRec.setParam(key, val) + return speechRec + + +def getSpeechAudioFormat(speechModel, inputAudioFormat): + try: + sampleFormat = getattr(gizmo.AVSampleFormat, + speechModel.get('sampleformat', 'S16')) + + sampleRate = speechModel.get('samplerate', 16000) + if type(sampleRate) == str: + sampleRate = int(sampleRate) + + channelLayout = 1 + ids = inputAudioFormat.getChannelNames().keys() + if len(ids) > 0: + channelLayout = list(ids)[0] + + return gizmo.AudioFormat(sampleFormat, sampleRate, channelLayout) + except: + raise Error(_('Invalid speech audio format')) + + +class MixMap: + def __init__(self, channelsNo = 0): + self.channelsNo = channelsNo + self.map = [ 0.0 ] * channelsNo * channelsNo + + def setPath(self, srcNo, dstNo, gain = 1.0): + self.map[srcNo + dstNo*self.channelsNo] = gain + + def mixAll(self, dstNo, gain = 1.0): + g = gain / self.channelsNo + for srcNo in range(self.channelsNo): + self.setPath(srcNo, dstNo, g) + + def getChannelNoByName(name, fmt): + no = 0 + for channel, chname in sorted(fmt.getChannelNames().items()): + if name == chname: + return no + no += 1 + return None + + def __repr__(self): + if self.channelsNo == 0: + return '' + if self.map == [ 0.0 ] * len(self.map): + return '' + + items = [] + no = self.channelsNo + for dst, srcs in enumerate([ self.map[ x*no : (x+1)*no ] for x in range(no) ]): + if srcs == [ 0.0 ] * no: + items.append('[0]=>{}'.format(dst)) + else: + items.append('{}=>{}'.format(srcs, dst)) + return ''.format(', '.join(items)) + diff --git a/subsync/stream.py b/subsync/stream.py new file mode 100644 index 0000000..fc7f4ca --- /dev/null +++ b/subsync/stream.py @@ -0,0 +1,83 @@ +import gizmo + + +class Stream(object): + def __init__(self, path=None, types=None, stream=None): + self.path = path + self.no = None + self.type = None + self.types = types + self.streams = {} + self.fps = None + self.lang = None + self.enc = None + + if path != None: + self.open(path) + + if stream != None: + self.assign(stream) + + def stream(self): + return self.streams[self.no] if self.no != None else None + + def open(self, path): + ss = gizmo.Demux(path).getStreamsInfo() + streams = {s.no: s for s in ss if self.types==None or s.type in self.types} + + self.path = path + self.streams = streams + self.no = None + self.lang = None + self.enc = None + self.fps = None + + for stream in ss: + if stream.frameRate: + self.fps = stream.frameRate + break + + if len(streams) > 0: + self.selectFirstMachingStream() + + def assign(self, s): + self.path = s.path + self.no = s.no + self.type = s.type + self.types = s.types + self.streams = s.streams + self.fps = s.fps + self.lang = s.lang + self.enc = s.enc + + def setNotNone(self, **kw): + for key, val in kw.items(): + if val != None: + setattr(self, key, val) + + def select(self, no): + stream = self.streams[no] + self.no = no + self.type = stream.type + self.lang = stream.lang + return stream + + def selectFirstMachingStream(self): + if self.streams == None or len(self.streams) == 0: + return None + if self.types == None: + return self.select(min(self.streams)) + for t in self.types: + for no in sorted(self.streams): + if self.streams[no].type == t: + return self.select(no) + + def isOpen(self): + return self.path != None + + def isSelect(self): + return self.path != None and self.no != None + + def __repr__(self): + return '{}:{}/{} type={} lang={} enc={}'.format( + self.path, self.no, len(self.streams), self.type, self.lang, self.enc) diff --git a/subsync/subtitle.py b/subsync/subtitle.py new file mode 100644 index 0000000..a1b4734 --- /dev/null +++ b/subsync/subtitle.py @@ -0,0 +1,127 @@ +from error import Error +import bisect +import pysubs2 +import copy +import re +import threading + +import logging +logger = logging.getLogger(__name__) + + +formatRe = re.compile(r'{\\.*?}') +newlineRe = re.compile(r'\\[nN]') + + +class Subtitles(pysubs2.SSAFile): + def __init__(self): + super().__init__() + self.out = None + + def add(self, begin, end, text): + entry = parseLine(text) + event = pysubs2.SSAEvent( + type = 'Dialogue', + start = begin * 1000.0, + end = end * 1000.0, + **entry) + self.insert(bisect.bisect_left(self, event), event) + + def synchronize(self, formula): + res = copy.deepcopy(self) + res.transform_framerate(formula.a*25.0, 25.0) + res.shift(s=formula.b) + return res + + def save(self, path, encoding=u'utf-8', fmt=None, fps=None): + if fmt == None and path.endswith('.txt'): + fmt = 'microdvd' + + try: + super().save(path, encoding=encoding, format_=fmt, fps=fps) + except pysubs2.exceptions.UnknownFileExtensionError as err: + if fmt != None: + return Error('Can\'t save subtitles file' + '\n' + str(err)) \ + .add('path', path) \ + .add('encodings', encoding) \ + .addn('format', fmt) \ + .addn('fps', fps) + else: + super().save(path, encoding=encoding, format_='microdvd', fps=fps) + + def getMaxChange(self, formula): + if len(self.events) > 0: + return max(abs(formula.getY(x) - x) for x in + (self.events[0].start/1000.0, self.events[-1].end/1000.0)) + else: + return 0.0 + + +class SubtitlesCollector(object): + def __init__(self): + self.subtitles = Subtitles() + self.subtitlesLock = threading.Lock() + + def __len__(self): + with self.subtitlesLock: + return len(self.subtitles) + + def addSubtitle(self, begin, end, text): + with self.subtitlesLock: + self.subtitles.add(begin, end, text) + + def getMaxSubtitleDiff(self, formula): + with self.subtitlesLock: + return self.subtitles.getMaxChange(formula) + + def getSubtitles(self): + return self.subtitles + + def getSynchronizedSubtitles(self, formula): + logger.info('subtitles synchronized with %s', str(formula)) + with self.subtitlesLock: + return self.subtitles.synchronize(formula) + + +def makeTimestamp(time, short=False, fraction=True): + t = int(time) + h = int(t / 3600) + m = int((t % 3600) / 60) + s = int(t % 60) + + if short: + if t < 3600: + res = '{:d}:{:02d}'.format(m, s); + else: + res = '{:d}:{:02d}:{:02d}'.format(h, m, s); + else: + res = '{:02d}:{:02d}:{:02d}'.format(h, m, s); + + if fraction: + ms = int((time % 1) * 1000) + res += '.{:03d}'.format(ms) + return res + + +def parseLine(text): + fields = text.split(',', 8) + if len(fields) == 9: + entry = { + 'style': fields[2], + 'name': fields[3], + 'marginl': fields[4], + 'marginr': fields[5], + 'marginv': fields[6], + 'effect': fields[7], + 'text': fields[8] } + else: + entry = { + 'style': 'Default', + 'text': text } + return entry + +def extractText(text, linesep='\n'): + global newLineRe + global formatRe + return newlineRe.sub(linesep, formatRe.sub('', parseLine(text)['text'])) + diff --git a/subsync/synchro.py b/subsync/synchro.py new file mode 100644 index 0000000..efa0d18 --- /dev/null +++ b/subsync/synchro.py @@ -0,0 +1,142 @@ +import gizmo +import pipeline +import subtitle +from settings import settings +import dictionary +import encdetect +import threading +import multiprocessing + +import logging +logger = logging.getLogger(__name__) + + +def getJobsNo(): + no = settings().jobsNo + if type(no) is int and no >= 1: + return no + else: + return max(multiprocessing.cpu_count(), 2) + + +class Synchronizer(object): + def __init__(self, listener, subs, refs): + self.listener = listener + self.subs = subs + self.refs = refs + + self.fps = refs.stream().frameRate + if self.fps == None: + self.fps = refs.fps + + self.correlator = gizmo.Correlator( + settings().windowSize, + settings().minCorrelation, + settings().maxPointDist, + settings().minPointsNo, + settings().minWordsSim) + + self.stats = gizmo.CorrelationStats() + self.statsLock = threading.Lock() + self.correlator.connectStatsCallback(self.onStatsUpdate) + + self.subtitlesCollector = subtitle.SubtitlesCollector() + + for stream in (subs, refs): + if stream.type == 'subtitle/text' and not stream.enc and len(stream.streams) == 1: + stream.enc = encdetect.detectEncoding(stream.path, stream.lang) + + self.subPipeline = pipeline.createProducerPipeline(subs) + self.subPipeline.connectEosCallback(self.onSubEos) + self.subPipeline.connectErrorCallback(self.onSubError) + self.subPipeline.connectSubsCallback(self.subtitlesCollector.addSubtitle, self.subtitlesCollector) + + if subs.lang and refs.lang and subs.lang != refs.lang: + self.dictionary = dictionary.loadDictionary(subs.lang, refs.lang, settings().minWordLen) + self.translator = gizmo.Translator(self.dictionary) + self.translator.setMinWordsSim(settings().minWordsSim) + self.subPipeline.connectWordsCallback(self.translator.pushWord, self.translator) + self.translator.connectWordsCallback(self.correlator.pushSubWord, self.correlator) + else: + self.subPipeline.connectWordsCallback(self.correlator.pushSubWord, self.correlator) + + self.refPipelines = pipeline.createProducerPipelines(refs, getJobsNo()) + for p in self.refPipelines: + p.connectEosCallback(self.onRefEos) + p.connectErrorCallback(self.onRefError) + p.connectWordsCallback(self.correlator.pushRefWord, self.correlator) + + self.pipelines = [ self.subPipeline ] + self.refPipelines + + def start(self): + logger.info('starting synchronization jobs') + self.correlator.start() + for p in self.pipelines: + p.start() + + def stop(self): + self.correlator.stop() + for p in self.pipelines: + p.stop() + + def isRunning(self): + if self.correlator.isRunning() and not self.correlator.isDone(): + return True + + for p in self.pipelines: + if p.isRunning(): + return True + + return False + + def getProgress(self): + psum = 0.0 + plen = 0 + + for pr in [ p.getProgress() for p in self.pipelines ]: + if pr != None: + psum += pr + plen += 1 + + cp = self.correlator.getProgress() + res = cp * cp + + if plen > 0: + res *= psum / plen + + return res + + def getStats(self): + with self.statsLock: + return self.stats + + def getMaxChange(self): + return self.subtitlesCollector.getMaxSubtitleDiff(self.getStats().formula) + + def getSynchronizedSubtitles(self): + return self.subtitlesCollector.getSynchronizedSubtitles(self.getStats().formula) + + def onStatsUpdate(self, stats): + logger.info(stats) + with self.statsLock: + self.stats = stats + + def onSubError(self, err): + logger.warning('SUB: %r', str(err).replace('\n', '; ')) + self.listener.onError('sub', err) + if 'terminated' in err.fields: + self.stop() + + def onRefError(self, err): + logger.warning('REF: %r', str(err).replace('\n', '; ')) + self.listener.onError('ref', err) + if 'terminated' in err.fields: + self.stop() + + def onSubEos(self): + logger.info('subtitle job terminated') + self.listener.onSubReady() + + def onRefEos(self): + logger.info('reference job terminated') + diff --git a/subsync/thread.py b/subsync/thread.py new file mode 100644 index 0000000..78ca48c --- /dev/null +++ b/subsync/thread.py @@ -0,0 +1,124 @@ +import wx +from functools import wraps +import threading + +import logging +logger = logging.getLogger(__name__) + + +class AtomicValue(object): + def __init__(self, value=None): + self.value = value + self.lock = threading.Lock() + + def set(self, value): + with self.lock: + self.value = value + + def get(self): + with self.lock: + return self.value + + def swap(self, newValue): + with self.lock: + value = self.value + self.value = newValue + return value + + +class AtomicInt(AtomicValue): + def __init__(self, value=0): + super().__init__(value) + + def up(self, num=1): + with self.lock: + self.value += num + + def down(self, num=1): + with self.lock: + self.value -= num + + +class Thread(object): + def __init__(self, target=None, done=None, error=None): + self._thread = None + self._running = AtomicValue(False) + self._target = target + self._done = done if done else self.done + self._error = error if error else self.error + + def start(self, *args, **kw): + self._running.set(True) + if self._thread == None or not self._thread.isAlive(): + if self._target == None: + self._target = self.run + self._thread = threading.Thread(target=self._run, *args, **kw) + self._thread.start() + + def stop(self, block=False): + self._running.set(False) + if block and self._thread: + self._thread.join() + + def isAlive(self): + return self._thread and self._thread.isAlive() + + def _run(self, *args, **kw): + logger.info('thread started') + try: + for x in self._target(*args, **kw): + if not self._running.get(): + break + + logger.info('thread terminated') + self._done() + + except Exception as e: + logger.warn('thread terminated with error %r', e, exc_info=True) + self._error(e) + + def done(self): + pass + + def error(self, err): + pass + + +def gui_thread(func): + '''Run in GUI thread + If function is called from main GUI thread, it is called immediately. + Otherwise it will be scheduled with wx.CallAfter + ''' + @wraps(func) + def wrapper(*args, **kwargs): + if wx.IsMainThread(): + func(*args, **kwargs) + else: + wx.CallAfter(lambda args, kwargs: func(*args, **kwargs), args, kwargs) + return wrapper + + +def gui_thread_cnt(counter_name): + '''Run in GUI thread, count pending actions with counter_name + Wraps object methods + If function is called from main GUI thread, it is called immediately. + Otherwise it will be scheduled with wx.CallAfter + self.counter_name will count scheduled actions + ''' + def decorator(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + if not hasattr(self, counter_name): + setattr(self, counter_name, AtomicInt()) + + if wx.IsMainThread(): + func(self, *args, **kwargs) + else: + counter = getattr(self, counter_name) + counter.up() + wx.CallAfter(lambda self, args, kwargs, counter: + [ counter.down(), func(self, *args, **kwargs) ], + self, args, kwargs, counter) + return wrapper + return decorator + diff --git a/subsync/utils.py b/subsync/utils.py new file mode 100644 index 0000000..b7c5f53 --- /dev/null +++ b/subsync/utils.py @@ -0,0 +1,32 @@ +import data.languages + + +def getLanguageName(lang): + name = data.languages.languages.get(lang, None) + if name: + return name[0] + return lang + + +def parvseVersion(version): + try: + return tuple(int(x) for x in version.split('.')) + except: + return None + + +def getCurrentVersion(): + try: + from version import version_short + return parvseVersion(version_short) + except: + return None + + +def fileSizeFmt(val): + for unit in ['B','kB','MB','GB']: + if val < 1000.0: + break + val /= 1000.0 + unit = 'TB' + return '{:.1f} {}'.format(val, unit) diff --git a/tools/dict/banner.txt b/tools/dict/banner.txt new file mode 100644 index 0000000..97291cf --- /dev/null +++ b/tools/dict/banner.txt @@ -0,0 +1,10 @@ + +# Data extracted from Wiktionary [1] by the DBnary project [2]. +# Converted to SQLite database for WikDikt online translator [3] +# and thanks to courtesy of Karl Bartel [4] used in here. +# +# 1. https://www.wiktionary.org +# 2. http://kaiko.getalp.org/about-dbnary +# 3. http://www.wikdict.com/page/contact +# 4. http://www.karl.berlin + diff --git a/tools/dict/dict-compile.py b/tools/dict/dict-compile.py new file mode 100644 index 0000000..c4e4f1f --- /dev/null +++ b/tools/dict/dict-compile.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +import os +import sys +import glob +from dict_tools import * + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print('use: {} [version]'.format(sys.argv[0])) + + os.makedirs('res', exist_ok=True) + version = sys.argv[1] + dicts = set() + + for fname in glob.glob('dict/*-*.dict'): + langs = os.path.basename(fname).split('.', 1)[0].split('-', 1) + dicts.add(tuple(sorted(langs))) + + banner = '' + if os.path.isfile('banner.txt'): + with open('banner.txt', 'rt') as fp: + banner = fp.read() + + for lang1, lang2 in dicts: + path1 = 'dict/{}-{}.dict'.format(lang1, lang2) + path2 = 'dict/{}-{}.dict'.format(lang2, lang1) + + d = {} + if os.path.isfile(path1): + print(path1, end='') + d = loadDict(path1) + else: + print('-' * len(path1), end='') + + if os.path.isfile(path2): + print(' + ' + path2, end='') + d2 = loadDict(path2) + d2 = transponseDict(d2) + d = mergeDicts(d, d2) + else: + print(' + ' + '-' * len(path2), end='') + + dstpath = 'res/{}-{}.dict'.format(lang1, lang2) + print(' => {} ({})'.format(dstpath, len(d)), end='') + + if len(d) >= 1000: + print('') + b = '#dictionary/{}/{}/{}\n{}'.format(lang1, lang2, version, banner) + saveDict(d, dstpath, b) + + else: + print('\tSKIPPING') + diff --git a/tools/dict/dict_tools.py b/tools/dict/dict_tools.py new file mode 100644 index 0000000..f7cf73b --- /dev/null +++ b/tools/dict/dict_tools.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +import sys + + +def addToDict(d, key, val): + if isinstance(val, set) or isinstance(val, list): + for v in val: + addToDict(d, key, v) + else: + if key not in d: + d[key] = set() + d[key].add(val) + return d + + +def mergeDicts(d1, d2): + res = {} + for key in d1: + addToDict(res, key, d1[key]) + for key in d2: + addToDict(res, key, d2[key]) + return res + + +def loadDict(fname): + d = {} + with open(fname, 'r') as file: + for line in file: + if (len(line) > 0) and (line[0] != '#'): + entrys = line.strip().split('|') + if len(entrys) >= 2: + key = entrys[0] + if key in d: + print('duplicated key: "' + key + '"') + addToDict(d, key, entrys[1:]) + else: + print('invalid entry: "' + line + '"') + return d + + +def saveDict(d, fname, banner=None): + with open(fname, 'w') as file: + if banner: + file.write(banner) + for key, vals in sorted(d.items()): + if len(vals) > 0: + file.write(key + '|' + '|'.join(vals) + '\n') + else: + print('empty translation: "' + key + '" => ()') + + +def transponseDict(d): + res = {} + for key in d: + for val in d[key]: + addToDict(res, val, key) + return res + + +def validateDict(d): + valsNo = 0 + err = 0 + for key in d: + if len(key.split()) != 1: + print('invalid key: "' + key + '"') + err += 1 + if len(d[key]) == 0: + print('no values for key: "' + key + '"') + err += 1 + for val in d[key]: + valsNo += 1 + if len(key.split()) != 1: + print('invalid value for key "' + key + '": "' + val + '"') + err += 1 + if err == 0: + errs = 'No' + else: + errs = str(err) + print(errs + ' errors detected, keys: ' + str(len(d)) + ', values: ' + str(valsNo)) + return err + + +if __name__ == "__main__": + print('Command line tools to use with python interpreter:') + print('\texecfile("' + sys.argv[0] + '")\n'); + print('Functions:') + print('\tdict = loadDict(fileName)') + print('\tsaveDict(dict, fileName)') + print('\taddToDict(dict, key, value)') + print('\tres = mergeDicts(dict1, dict2)') + print('\tdict2 = transposeDict(dict)') + print('\tvalidateDict(dict)') diff --git a/tools/dict/language-codes-3b2.csv b/tools/dict/language-codes-3b2.csv new file mode 100644 index 0000000..863c284 --- /dev/null +++ b/tools/dict/language-codes-3b2.csv @@ -0,0 +1,184 @@ +"aar","aa","Afar" +"abk","ab","Abkhazian" +"afr","af","Afrikaans" +"aka","ak","Akan" +"alb","sq","Albanian" +"amh","am","Amharic" +"ara","ar","Arabic" +"arg","an","Aragonese" +"arm","hy","Armenian" +"asm","as","Assamese" +"ava","av","Avaric" +"ave","ae","Avestan" +"aym","ay","Aymara" +"aze","az","Azerbaijani" +"bak","ba","Bashkir" +"bam","bm","Bambara" +"baq","eu","Basque" +"bel","be","Belarusian" +"ben","bn","Bengali" +"bih","bh","Bihari languages" +"bis","bi","Bislama" +"bos","bs","Bosnian" +"bre","br","Breton" +"bul","bg","Bulgarian" +"bur","my","Burmese" +"cat","ca","Catalan; Valencian" +"cha","ch","Chamorro" +"che","ce","Chechen" +"chi","zh","Chinese" +"chu","cu","Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic" +"chv","cv","Chuvash" +"cor","kw","Cornish" +"cos","co","Corsican" +"cre","cr","Cree" +"cze","cs","Czech" +"dan","da","Danish" +"div","dv","Divehi; Dhivehi; Maldivian" +"dut","nl","Dutch; Flemish" +"dzo","dz","Dzongkha" +"eng","en","English" +"epo","eo","Esperanto" +"est","et","Estonian" +"ewe","ee","Ewe" +"fao","fo","Faroese" +"fij","fj","Fijian" +"fin","fi","Finnish" +"fre","fr","French" +"fry","fy","Western Frisian" +"ful","ff","Fulah" +"geo","ka","Georgian" +"ger","de","German" +"gla","gd","Gaelic; Scottish Gaelic" +"gle","ga","Irish" +"glg","gl","Galician" +"glv","gv","Manx" +"gre","el","Greek, Modern (1453-)" +"grn","gn","Guarani" +"guj","gu","Gujarati" +"hat","ht","Haitian; Haitian Creole" +"hau","ha","Hausa" +"heb","he","Hebrew" +"her","hz","Herero" +"hin","hi","Hindi" +"hmo","ho","Hiri Motu" +"hrv","hr","Croatian" +"hun","hu","Hungarian" +"ibo","ig","Igbo" +"ice","is","Icelandic" +"ido","io","Ido" +"iii","ii","Sichuan Yi; Nuosu" +"iku","iu","Inuktitut" +"ile","ie","Interlingue; Occidental" +"ina","ia","Interlingua (International Auxiliary Language Association)" +"ind","id","Indonesian" +"ipk","ik","Inupiaq" +"ita","it","Italian" +"jav","jv","Javanese" +"jpn","ja","Japanese" +"kal","kl","Kalaallisut; Greenlandic" +"kan","kn","Kannada" +"kas","ks","Kashmiri" +"kau","kr","Kanuri" +"kaz","kk","Kazakh" +"khm","km","Central Khmer" +"kik","ki","Kikuyu; Gikuyu" +"kin","rw","Kinyarwanda" +"kir","ky","Kirghiz; Kyrgyz" +"kom","kv","Komi" +"kon","kg","Kongo" +"kor","ko","Korean" +"kua","kj","Kuanyama; Kwanyama" +"kur","ku","Kurdish" +"lao","lo","Lao" +"lat","la","Latin" +"lav","lv","Latvian" +"lim","li","Limburgan; Limburger; Limburgish" +"lin","ln","Lingala" +"lit","lt","Lithuanian" +"ltz","lb","Luxembourgish; Letzeburgesch" +"lub","lu","Luba-Katanga" +"lug","lg","Ganda" +"mac","mk","Macedonian" +"mah","mh","Marshallese" +"mal","ml","Malayalam" +"mao","mi","Maori" +"mar","mr","Marathi" +"may","ms","Malay" +"mlg","mg","Malagasy" +"mlt","mt","Maltese" +"mon","mn","Mongolian" +"nau","na","Nauru" +"nav","nv","Navajo; Navaho" +"nbl","nr","Ndebele, South; South Ndebele" +"nde","nd","Ndebele, North; North Ndebele" +"ndo","ng","Ndonga" +"nep","ne","Nepali" +"nno","nn","Norwegian Nynorsk; Nynorsk, Norwegian" +"nob","nb","Bokmål, Norwegian; Norwegian Bokmål" +"nor","no","Norwegian" +"nya","ny","Chichewa; Chewa; Nyanja" +"oci","oc","Occitan (post 1500); Provençal" +"oji","oj","Ojibwa" +"ori","or","Oriya" +"orm","om","Oromo" +"oss","os","Ossetian; Ossetic" +"pan","pa","Panjabi; Punjabi" +"per","fa","Persian" +"pli","pi","Pali" +"pol","pl","Polish" +"por","pt","Portuguese" +"pus","ps","Pushto; Pashto" +"que","qu","Quechua" +"roh","rm","Romansh" +"rum","ro","Romanian; Moldavian; Moldovan" +"run","rn","Rundi" +"rus","ru","Russian" +"sag","sg","Sango" +"san","sa","Sanskrit" +"sin","si","Sinhala; Sinhalese" +"slo","sk","Slovak" +"slv","sl","Slovenian" +"sme","se","Northern Sami" +"smo","sm","Samoan" +"sna","sn","Shona" +"snd","sd","Sindhi" +"som","so","Somali" +"sot","st","Sotho, Southern" +"spa","es","Spanish; Castilian" +"srd","sc","Sardinian" +"srp","sr","Serbian" +"ssw","ss","Swati" +"sun","su","Sundanese" +"swa","sw","Swahili" +"swe","sv","Swedish" +"tah","ty","Tahitian" +"tam","ta","Tamil" +"tat","tt","Tatar" +"tel","te","Telugu" +"tgk","tg","Tajik" +"tgl","tl","Tagalog" +"tha","th","Thai" +"tib","bo","Tibetan" +"tir","ti","Tigrinya" +"ton","to","Tonga (Tonga Islands)" +"tsn","tn","Tswana" +"tso","ts","Tsonga" +"tuk","tk","Turkmen" +"tur","tr","Turkish" +"twi","tw","Twi" +"uig","ug","Uighur; Uyghur" +"ukr","uk","Ukrainian" +"urd","ur","Urdu" +"uzb","uz","Uzbek" +"ven","ve","Venda" +"vie","vi","Vietnamese" +"vol","vo","Volapük" +"wel","cy","Welsh" +"wln","wa","Walloon" +"wol","wo","Wolof" +"xho","xh","Xhosa" +"yid","yi","Yiddish" +"yor","yo","Yoruba" +"zha","za","Zhuang; Chuang" +"zul","zu","Zulu" diff --git a/tools/dict/sqlite2dict.py b/tools/dict/sqlite2dict.py new file mode 100644 index 0000000..477d78f --- /dev/null +++ b/tools/dict/sqlite2dict.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import sys +import sqlite3 +from dict_tools import * + + +def readDictFromSqliteDB(fname): + d = {} + db = sqlite3.connect(fname) + cursor = db.cursor() + cursor.execute('select written_rep, trans_list from translation') + #where part_of_speech in ( + #'noun', 'properNoun', 'adjective', 'adverb', 'interjection', + #'possessiveAdjective', 'conjunction', 'verb', 'particle', 'preposition', + #'modal', 'pronoun', 'indefinitePronoun', 'numeral', + #'interrogativePronoun', 'indefiniteCardinalNumeral', + #'multiplicativeNumeral', 'personalPronoun', 'cardinalNumeral', + #'collective', 'participleAdjective', 'numeralFraction'); ''') + for row in cursor: + key = row[0].strip() + vals = row[1].split('|') + if key.find(' ') == -1: + for val in vals: + val = val.strip() + if val.find(' ') == -1: + addToDict(d, key, val) + return d + + +if __name__ == "__main__": + if len(sys.argv) == 3: + d = readDictFromSqliteDB(sys.argv[1]) + validateDict(d) + saveDict(d, sys.argv[2]) + else: + print('Usage:') + print('\t' + sys.argv[0] + ' infile.sqlite3 outfile.dict') diff --git a/tools/dict/wikdict-convert.py b/tools/dict/wikdict-convert.py new file mode 100644 index 0000000..084c250 --- /dev/null +++ b/tools/dict/wikdict-convert.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import sys +import os +import glob +import csv +from sqlite2dict import * + + +if __name__ == "__main__": + codes = {} + + os.makedirs('dict', exist_ok=True) + + with open('language-codes-3b2.csv', 'rt') as file: + reader = csv.reader(file) + for row in reader: + codes[row[1]] = row[0] + + for srcname in glob.glob('wikdict/*-*.sqlite3'): + name = os.path.basename(srcname).split('.', 1)[0] + langs = name.split('-', 1) + + try: + dstname = 'dict/' + codes[langs[0]] + '-' + codes[langs[1]] + '.dict' + + print(srcname + ' => ' + dstname) + d = readDictFromSqliteDB(srcname) + validateDict(d) + if len(d) > 0: + saveDict(d, dstname) + else: + print('empty dict, skipping') + except Exception as e: + print('error: ' + str(e)) + print('') + diff --git a/tools/update_version.py b/tools/update_version.py new file mode 100644 index 0000000..e2716a8 --- /dev/null +++ b/tools/update_version.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import sys +import subprocess +import re + + +DEFAULT_FNAME = 'version.py' + +def update_version(fname = DEFAULT_FNAME): + try: + version_long = subprocess.check_output(['git', 'describe', '--tags']).decode('UTF-8').strip() + v = version_long[re.search('\d', version_long).start():].split('-') + if len(v) > 1: + version = '{}.{}'.format(v[0], v[1]) + else: + version = '{}.0'.format(v[0]) + except Exception as e: + print('Version not recognized, using default, reason: ' + str(e)) + version_long = 'custom' + version = '0.0.0' + + print('version number: {} ({})'.format(version, version_long)) + + with open(fname, 'w') as fp: + fp.write('version = "{}"\n'.format(version_long)) + fp.write('version_short = "{}"\n'.format(version)) + + return version + + +if __name__ == "__main__": + fname = DEFAULT_FNAME + if len(sys.argv) > 1: + fname = sys.argv[1] + update_version(fname) +