Initial commit
This commit is contained in:
		
						commit
						3e6a08231c
					
				
					 17 changed files with 2054 additions and 0 deletions
				
			
		
							
								
								
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| *.swp | ||||
| *.pyc | ||||
| __pycache__ | ||||
| .pytest_cache | ||||
| dist | ||||
							
								
								
									
										674
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										674
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,674 @@ | |||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
| 
 | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
| 
 | ||||
|                             Preamble | ||||
| 
 | ||||
|   The GNU General Public License is a free, copyleft license for | ||||
| software and other kinds of works. | ||||
| 
 | ||||
|   The licenses for most software and other practical works are designed | ||||
| to take away your freedom to share and change the works.  By contrast, | ||||
| the GNU General Public License is intended to guarantee your freedom to | ||||
| share and change all versions of a program--to make sure it remains free | ||||
| software for all its users.  We, the Free Software Foundation, use the | ||||
| GNU General Public License for most of our software; it applies also to | ||||
| any other work released this way by its authors.  You can apply it to | ||||
| your programs, too. | ||||
| 
 | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| price.  Our General Public Licenses are designed to make sure that you | ||||
| have the freedom to distribute copies of free software (and charge for | ||||
| them if you wish), that you receive source code or can get it if you | ||||
| want it, that you can change the software or use pieces of it in new | ||||
| free programs, and that you know you can do these things. | ||||
| 
 | ||||
|   To protect your rights, we need to prevent others from denying you | ||||
| these rights or asking you to surrender the rights.  Therefore, you have | ||||
| certain responsibilities if you distribute copies of the software, or if | ||||
| you modify it: responsibilities to respect the freedom of others. | ||||
| 
 | ||||
|   For example, if you distribute copies of such a program, whether | ||||
| gratis or for a fee, you must pass on to the recipients the same | ||||
| freedoms that you received.  You must make sure that they, too, receive | ||||
| or can get the source code.  And you must show them these terms so they | ||||
| know their rights. | ||||
| 
 | ||||
|   Developers that use the GNU GPL protect your rights with two steps: | ||||
| (1) assert copyright on the software, and (2) offer you this License | ||||
| giving you legal permission to copy, distribute and/or modify it. | ||||
| 
 | ||||
|   For the developers' and authors' protection, the GPL clearly explains | ||||
| that there is no warranty for this free software.  For both users' and | ||||
| authors' sake, the GPL requires that modified versions be marked as | ||||
| changed, so that their problems will not be attributed erroneously to | ||||
| authors of previous versions. | ||||
| 
 | ||||
|   Some devices are designed to deny users access to install or run | ||||
| modified versions of the software inside them, although the manufacturer | ||||
| can do so.  This is fundamentally incompatible with the aim of | ||||
| protecting users' freedom to change the software.  The systematic | ||||
| pattern of such abuse occurs in the area of products for individuals to | ||||
| use, which is precisely where it is most unacceptable.  Therefore, we | ||||
| have designed this version of the GPL to prohibit the practice for those | ||||
| products.  If such problems arise substantially in other domains, we | ||||
| stand ready to extend this provision to those domains in future versions | ||||
| of the GPL, as needed to protect the freedom of users. | ||||
| 
 | ||||
|   Finally, every program is threatened constantly by software patents. | ||||
| States should not allow patents to restrict development and use of | ||||
| software on general-purpose computers, but in those that do, we wish to | ||||
| avoid the special danger that patents applied to a free program could | ||||
| make it effectively proprietary.  To prevent this, the GPL assures that | ||||
| patents cannot be used to render the program non-free. | ||||
| 
 | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
| 
 | ||||
|                        TERMS AND CONDITIONS | ||||
| 
 | ||||
|   0. Definitions. | ||||
| 
 | ||||
|   "This License" refers to version 3 of the GNU General Public License. | ||||
| 
 | ||||
|   "Copyright" also means copyright-like laws that apply to other kinds of | ||||
| works, such as semiconductor masks. | ||||
| 
 | ||||
|   "The Program" refers to any copyrightable work licensed under this | ||||
| License.  Each licensee is addressed as "you".  "Licensees" and | ||||
| "recipients" may be individuals or organizations. | ||||
| 
 | ||||
|   To "modify" a work means to copy from or adapt all or part of the work | ||||
| in a fashion requiring copyright permission, other than the making of an | ||||
| exact copy.  The resulting work is called a "modified version" of the | ||||
| earlier work or a work "based on" the earlier work. | ||||
| 
 | ||||
|   A "covered work" means either the unmodified Program or a work based | ||||
| on the Program. | ||||
| 
 | ||||
|   To "propagate" a work means to do anything with it that, without | ||||
| permission, would make you directly or secondarily liable for | ||||
| infringement under applicable copyright law, except executing it on a | ||||
| computer or modifying a private copy.  Propagation includes copying, | ||||
| distribution (with or without modification), making available to the | ||||
| public, and in some countries other activities as well. | ||||
| 
 | ||||
|   To "convey" a work means any kind of propagation that enables other | ||||
| parties to make or receive copies.  Mere interaction with a user through | ||||
| a computer network, with no transfer of a copy, is not conveying. | ||||
| 
 | ||||
|   An interactive user interface displays "Appropriate Legal Notices" | ||||
| to the extent that it includes a convenient and prominently visible | ||||
| feature that (1) displays an appropriate copyright notice, and (2) | ||||
| tells the user that there is no warranty for the work (except to the | ||||
| extent that warranties are provided), that licensees may convey the | ||||
| work under this License, and how to view a copy of this License.  If | ||||
| the interface presents a list of user commands or options, such as a | ||||
| menu, a prominent item in the list meets this criterion. | ||||
| 
 | ||||
|   1. Source Code. | ||||
| 
 | ||||
|   The "source code" for a work means the preferred form of the work | ||||
| for making modifications to it.  "Object code" means any non-source | ||||
| form of a work. | ||||
| 
 | ||||
|   A "Standard Interface" means an interface that either is an official | ||||
| standard defined by a recognized standards body, or, in the case of | ||||
| interfaces specified for a particular programming language, one that | ||||
| is widely used among developers working in that language. | ||||
| 
 | ||||
|   The "System Libraries" of an executable work include anything, other | ||||
| than the work as a whole, that (a) is included in the normal form of | ||||
| packaging a Major Component, but which is not part of that Major | ||||
| Component, and (b) serves only to enable use of the work with that | ||||
| Major Component, or to implement a Standard Interface for which an | ||||
| implementation is available to the public in source code form.  A | ||||
| "Major Component", in this context, means a major essential component | ||||
| (kernel, window system, and so on) of the specific operating system | ||||
| (if any) on which the executable work runs, or a compiler used to | ||||
| produce the work, or an object code interpreter used to run it. | ||||
| 
 | ||||
|   The "Corresponding Source" for a work in object code form means all | ||||
| the source code needed to generate, install, and (for an executable | ||||
| work) run the object code and to modify the work, including scripts to | ||||
| control those activities.  However, it does not include the work's | ||||
| System Libraries, or general-purpose tools or generally available free | ||||
| programs which are used unmodified in performing those activities but | ||||
| which are not part of the work.  For example, Corresponding Source | ||||
| includes interface definition files associated with source files for | ||||
| the work, and the source code for shared libraries and dynamically | ||||
| linked subprograms that the work is specifically designed to require, | ||||
| such as by intimate data communication or control flow between those | ||||
| subprograms and other parts of the work. | ||||
| 
 | ||||
|   The Corresponding Source need not include anything that users | ||||
| can regenerate automatically from other parts of the Corresponding | ||||
| Source. | ||||
| 
 | ||||
|   The Corresponding Source for a work in source code form is that | ||||
| same work. | ||||
| 
 | ||||
|   2. Basic Permissions. | ||||
| 
 | ||||
|   All rights granted under this License are granted for the term of | ||||
| copyright on the Program, and are irrevocable provided the stated | ||||
| conditions are met.  This License explicitly affirms your unlimited | ||||
| permission to run the unmodified Program.  The output from running a | ||||
| covered work is covered by this License only if the output, given its | ||||
| content, constitutes a covered work.  This License acknowledges your | ||||
| rights of fair use or other equivalent, as provided by copyright law. | ||||
| 
 | ||||
|   You may make, run and propagate covered works that you do not | ||||
| convey, without conditions so long as your license otherwise remains | ||||
| in force.  You may convey covered works to others for the sole purpose | ||||
| of having them make modifications exclusively for you, or provide you | ||||
| with facilities for running those works, provided that you comply with | ||||
| the terms of this License in conveying all material for which you do | ||||
| not control copyright.  Those thus making or running the covered works | ||||
| for you must do so exclusively on your behalf, under your direction | ||||
| and control, on terms that prohibit them from making any copies of | ||||
| your copyrighted material outside their relationship with you. | ||||
| 
 | ||||
|   Conveying under any other circumstances is permitted solely under | ||||
| the conditions stated below.  Sublicensing is not allowed; section 10 | ||||
| makes it unnecessary. | ||||
| 
 | ||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||||
| 
 | ||||
|   No covered work shall be deemed part of an effective technological | ||||
| measure under any applicable law fulfilling obligations under article | ||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||||
| similar laws prohibiting or restricting circumvention of such | ||||
| measures. | ||||
| 
 | ||||
|   When you convey a covered work, you waive any legal power to forbid | ||||
| circumvention of technological measures to the extent such circumvention | ||||
| is effected by exercising rights under this License with respect to | ||||
| the covered work, and you disclaim any intention to limit operation or | ||||
| modification of the work as a means of enforcing, against the work's | ||||
| users, your or third parties' legal rights to forbid circumvention of | ||||
| technological measures. | ||||
| 
 | ||||
|   4. Conveying Verbatim Copies. | ||||
| 
 | ||||
|   You may convey verbatim copies of the Program's source code as you | ||||
| receive it, in any medium, provided that you conspicuously and | ||||
| appropriately publish on each copy an appropriate copyright notice; | ||||
| keep intact all notices stating that this License and any | ||||
| non-permissive terms added in accord with section 7 apply to the code; | ||||
| keep intact all notices of the absence of any warranty; and give all | ||||
| recipients a copy of this License along with the Program. | ||||
| 
 | ||||
|   You may charge any price or no price for each copy that you convey, | ||||
| and you may offer support or warranty protection for a fee. | ||||
| 
 | ||||
|   5. Conveying Modified Source Versions. | ||||
| 
 | ||||
|   You may convey a work based on the Program, or the modifications to | ||||
| produce it from the Program, in the form of source code under the | ||||
| terms of section 4, provided that you also meet all of these conditions: | ||||
| 
 | ||||
|     a) The work must carry prominent notices stating that you modified | ||||
|     it, and giving a relevant date. | ||||
| 
 | ||||
|     b) The work must carry prominent notices stating that it is | ||||
|     released under this License and any conditions added under section | ||||
|     7.  This requirement modifies the requirement in section 4 to | ||||
|     "keep intact all notices". | ||||
| 
 | ||||
|     c) You must license the entire work, as a whole, under this | ||||
|     License to anyone who comes into possession of a copy.  This | ||||
|     License will therefore apply, along with any applicable section 7 | ||||
|     additional terms, to the whole of the work, and all its parts, | ||||
|     regardless of how they are packaged.  This License gives no | ||||
|     permission to license the work in any other way, but it does not | ||||
|     invalidate such permission if you have separately received it. | ||||
| 
 | ||||
|     d) If the work has interactive user interfaces, each must display | ||||
|     Appropriate Legal Notices; however, if the Program has interactive | ||||
|     interfaces that do not display Appropriate Legal Notices, your | ||||
|     work need not make them do so. | ||||
| 
 | ||||
|   A compilation of a covered work with other separate and independent | ||||
| works, which are not by their nature extensions of the covered work, | ||||
| and which are not combined with it such as to form a larger program, | ||||
| in or on a volume of a storage or distribution medium, is called an | ||||
| "aggregate" if the compilation and its resulting copyright are not | ||||
| used to limit the access or legal rights of the compilation's users | ||||
| beyond what the individual works permit.  Inclusion of a covered work | ||||
| in an aggregate does not cause this License to apply to the other | ||||
| parts of the aggregate. | ||||
| 
 | ||||
|   6. Conveying Non-Source Forms. | ||||
| 
 | ||||
|   You may convey a covered work in object code form under the terms | ||||
| of sections 4 and 5, provided that you also convey the | ||||
| machine-readable Corresponding Source under the terms of this License, | ||||
| in one of these ways: | ||||
| 
 | ||||
|     a) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by the | ||||
|     Corresponding Source fixed on a durable physical medium | ||||
|     customarily used for software interchange. | ||||
| 
 | ||||
|     b) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by a | ||||
|     written offer, valid for at least three years and valid for as | ||||
|     long as you offer spare parts or customer support for that product | ||||
|     model, to give anyone who possesses the object code either (1) a | ||||
|     copy of the Corresponding Source for all the software in the | ||||
|     product that is covered by this License, on a durable physical | ||||
|     medium customarily used for software interchange, for a price no | ||||
|     more than your reasonable cost of physically performing this | ||||
|     conveying of source, or (2) access to copy the | ||||
|     Corresponding Source from a network server at no charge. | ||||
| 
 | ||||
|     c) Convey individual copies of the object code with a copy of the | ||||
|     written offer to provide the Corresponding Source.  This | ||||
|     alternative is allowed only occasionally and noncommercially, and | ||||
|     only if you received the object code with such an offer, in accord | ||||
|     with subsection 6b. | ||||
| 
 | ||||
|     d) Convey the object code by offering access from a designated | ||||
|     place (gratis or for a charge), and offer equivalent access to the | ||||
|     Corresponding Source in the same way through the same place at no | ||||
|     further charge.  You need not require recipients to copy the | ||||
|     Corresponding Source along with the object code.  If the place to | ||||
|     copy the object code is a network server, the Corresponding Source | ||||
|     may be on a different server (operated by you or a third party) | ||||
|     that supports equivalent copying facilities, provided you maintain | ||||
|     clear directions next to the object code saying where to find the | ||||
|     Corresponding Source.  Regardless of what server hosts the | ||||
|     Corresponding Source, you remain obligated to ensure that it is | ||||
|     available for as long as needed to satisfy these requirements. | ||||
| 
 | ||||
|     e) Convey the object code using peer-to-peer transmission, provided | ||||
|     you inform other peers where the object code and Corresponding | ||||
|     Source of the work are being offered to the general public at no | ||||
|     charge under subsection 6d. | ||||
| 
 | ||||
|   A separable portion of the object code, whose source code is excluded | ||||
| from the Corresponding Source as a System Library, need not be | ||||
| included in conveying the object code work. | ||||
| 
 | ||||
|   A "User Product" is either (1) a "consumer product", which means any | ||||
| tangible personal property which is normally used for personal, family, | ||||
| or household purposes, or (2) anything designed or sold for incorporation | ||||
| into a dwelling.  In determining whether a product is a consumer product, | ||||
| doubtful cases shall be resolved in favor of coverage.  For a particular | ||||
| product received by a particular user, "normally used" refers to a | ||||
| typical or common use of that class of product, regardless of the status | ||||
| of the particular user or of the way in which the particular user | ||||
| actually uses, or expects or is expected to use, the product.  A product | ||||
| is a consumer product regardless of whether the product has substantial | ||||
| commercial, industrial or non-consumer uses, unless such uses represent | ||||
| the only significant mode of use of the product. | ||||
| 
 | ||||
|   "Installation Information" for a User Product means any methods, | ||||
| procedures, authorization keys, or other information required to install | ||||
| and execute modified versions of a covered work in that User Product from | ||||
| a modified version of its Corresponding Source.  The information must | ||||
| suffice to ensure that the continued functioning of the modified object | ||||
| code is in no case prevented or interfered with solely because | ||||
| modification has been made. | ||||
| 
 | ||||
|   If you convey an object code work under this section in, or with, or | ||||
| specifically for use in, a User Product, and the conveying occurs as | ||||
| part of a transaction in which the right of possession and use of the | ||||
| User Product is transferred to the recipient in perpetuity or for a | ||||
| fixed term (regardless of how the transaction is characterized), the | ||||
| Corresponding Source conveyed under this section must be accompanied | ||||
| by the Installation Information.  But this requirement does not apply | ||||
| if neither you nor any third party retains the ability to install | ||||
| modified object code on the User Product (for example, the work has | ||||
| been installed in ROM). | ||||
| 
 | ||||
|   The requirement to provide Installation Information does not include a | ||||
| requirement to continue to provide support service, warranty, or updates | ||||
| for a work that has been modified or installed by the recipient, or for | ||||
| the User Product in which it has been modified or installed.  Access to a | ||||
| network may be denied when the modification itself materially and | ||||
| adversely affects the operation of the network or violates the rules and | ||||
| protocols for communication across the network. | ||||
| 
 | ||||
|   Corresponding Source conveyed, and Installation Information provided, | ||||
| in accord with this section must be in a format that is publicly | ||||
| documented (and with an implementation available to the public in | ||||
| source code form), and must require no special password or key for | ||||
| unpacking, reading or copying. | ||||
| 
 | ||||
|   7. Additional Terms. | ||||
| 
 | ||||
|   "Additional permissions" are terms that supplement the terms of this | ||||
| License by making exceptions from one or more of its conditions. | ||||
| Additional permissions that are applicable to the entire Program shall | ||||
| be treated as though they were included in this License, to the extent | ||||
| that they are valid under applicable law.  If additional permissions | ||||
| apply only to part of the Program, that part may be used separately | ||||
| under those permissions, but the entire Program remains governed by | ||||
| this License without regard to the additional permissions. | ||||
| 
 | ||||
|   When you convey a copy of a covered work, you may at your option | ||||
| remove any additional permissions from that copy, or from any part of | ||||
| it.  (Additional permissions may be written to require their own | ||||
| removal in certain cases when you modify the work.)  You may place | ||||
| additional permissions on material, added by you to a covered work, | ||||
| for which you have or can give appropriate copyright permission. | ||||
| 
 | ||||
|   Notwithstanding any other provision of this License, for material you | ||||
| add to a covered work, you may (if authorized by the copyright holders of | ||||
| that material) supplement the terms of this License with terms: | ||||
| 
 | ||||
|     a) Disclaiming warranty or limiting liability differently from the | ||||
|     terms of sections 15 and 16 of this License; or | ||||
| 
 | ||||
|     b) Requiring preservation of specified reasonable legal notices or | ||||
|     author attributions in that material or in the Appropriate Legal | ||||
|     Notices displayed by works containing it; or | ||||
| 
 | ||||
|     c) Prohibiting misrepresentation of the origin of that material, or | ||||
|     requiring that modified versions of such material be marked in | ||||
|     reasonable ways as different from the original version; or | ||||
| 
 | ||||
|     d) Limiting the use for publicity purposes of names of licensors or | ||||
|     authors of the material; or | ||||
| 
 | ||||
|     e) Declining to grant rights under trademark law for use of some | ||||
|     trade names, trademarks, or service marks; or | ||||
| 
 | ||||
|     f) Requiring indemnification of licensors and authors of that | ||||
|     material by anyone who conveys the material (or modified versions of | ||||
|     it) with contractual assumptions of liability to the recipient, for | ||||
|     any liability that these contractual assumptions directly impose on | ||||
|     those licensors and authors. | ||||
| 
 | ||||
|   All other non-permissive additional terms are considered "further | ||||
| restrictions" within the meaning of section 10.  If the Program as you | ||||
| received it, or any part of it, contains a notice stating that it is | ||||
| governed by this License along with a term that is a further | ||||
| restriction, you may remove that term.  If a license document contains | ||||
| a further restriction but permits relicensing or conveying under this | ||||
| License, you may add to a covered work material governed by the terms | ||||
| of that license document, provided that the further restriction does | ||||
| not survive such relicensing or conveying. | ||||
| 
 | ||||
|   If you add terms to a covered work in accord with this section, you | ||||
| must place, in the relevant source files, a statement of the | ||||
| additional terms that apply to those files, or a notice indicating | ||||
| where to find the applicable terms. | ||||
| 
 | ||||
|   Additional terms, permissive or non-permissive, may be stated in the | ||||
| form of a separately written license, or stated as exceptions; | ||||
| the above requirements apply either way. | ||||
| 
 | ||||
|   8. Termination. | ||||
| 
 | ||||
|   You may not propagate or modify a covered work except as expressly | ||||
| provided under this License.  Any attempt otherwise to propagate or | ||||
| modify it is void, and will automatically terminate your rights under | ||||
| this License (including any patent licenses granted under the third | ||||
| paragraph of section 11). | ||||
| 
 | ||||
|   However, if you cease all violation of this License, then your | ||||
| license from a particular copyright holder is reinstated (a) | ||||
| provisionally, unless and until the copyright holder explicitly and | ||||
| finally terminates your license, and (b) permanently, if the copyright | ||||
| holder fails to notify you of the violation by some reasonable means | ||||
| prior to 60 days after the cessation. | ||||
| 
 | ||||
|   Moreover, your license from a particular copyright holder is | ||||
| reinstated permanently if the copyright holder notifies you of the | ||||
| violation by some reasonable means, this is the first time you have | ||||
| received notice of violation of this License (for any work) from that | ||||
| copyright holder, and you cure the violation prior to 30 days after | ||||
| your receipt of the notice. | ||||
| 
 | ||||
|   Termination of your rights under this section does not terminate the | ||||
| licenses of parties who have received copies or rights from you under | ||||
| this License.  If your rights have been terminated and not permanently | ||||
| reinstated, you do not qualify to receive new licenses for the same | ||||
| material under section 10. | ||||
| 
 | ||||
|   9. Acceptance Not Required for Having Copies. | ||||
| 
 | ||||
|   You are not required to accept this License in order to receive or | ||||
| run a copy of the Program.  Ancillary propagation of a covered work | ||||
| occurring solely as a consequence of using peer-to-peer transmission | ||||
| to receive a copy likewise does not require acceptance.  However, | ||||
| nothing other than this License grants you permission to propagate or | ||||
| modify any covered work.  These actions infringe copyright if you do | ||||
| not accept this License.  Therefore, by modifying or propagating a | ||||
| covered work, you indicate your acceptance of this License to do so. | ||||
| 
 | ||||
|   10. Automatic Licensing of Downstream Recipients. | ||||
| 
 | ||||
|   Each time you convey a covered work, the recipient automatically | ||||
| receives a license from the original licensors, to run, modify and | ||||
| propagate that work, subject to this License.  You are not responsible | ||||
| for enforcing compliance by third parties with this License. | ||||
| 
 | ||||
|   An "entity transaction" is a transaction transferring control of an | ||||
| organization, or substantially all assets of one, or subdividing an | ||||
| organization, or merging organizations.  If propagation of a covered | ||||
| work results from an entity transaction, each party to that | ||||
| transaction who receives a copy of the work also receives whatever | ||||
| licenses to the work the party's predecessor in interest had or could | ||||
| give under the previous paragraph, plus a right to possession of the | ||||
| Corresponding Source of the work from the predecessor in interest, if | ||||
| the predecessor has it or can get it with reasonable efforts. | ||||
| 
 | ||||
|   You may not impose any further restrictions on the exercise of the | ||||
| rights granted or affirmed under this License.  For example, you may | ||||
| not impose a license fee, royalty, or other charge for exercise of | ||||
| rights granted under this License, and you may not initiate litigation | ||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | ||||
| any patent claim is infringed by making, using, selling, offering for | ||||
| sale, or importing the Program or any portion of it. | ||||
| 
 | ||||
|   11. Patents. | ||||
| 
 | ||||
|   A "contributor" is a copyright holder who authorizes use under this | ||||
| License of the Program or a work on which the Program is based.  The | ||||
| work thus licensed is called the contributor's "contributor version". | ||||
| 
 | ||||
|   A contributor's "essential patent claims" are all patent claims | ||||
| owned or controlled by the contributor, whether already acquired or | ||||
| hereafter acquired, that would be infringed by some manner, permitted | ||||
| by this License, of making, using, or selling its contributor version, | ||||
| but do not include claims that would be infringed only as a | ||||
| consequence of further modification of the contributor version.  For | ||||
| purposes of this definition, "control" includes the right to grant | ||||
| patent sublicenses in a manner consistent with the requirements of | ||||
| this License. | ||||
| 
 | ||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free | ||||
| patent license under the contributor's essential patent claims, to | ||||
| make, use, sell, offer for sale, import and otherwise run, modify and | ||||
| propagate the contents of its contributor version. | ||||
| 
 | ||||
|   In the following three paragraphs, a "patent license" is any express | ||||
| agreement or commitment, however denominated, not to enforce a patent | ||||
| (such as an express permission to practice a patent or covenant not to | ||||
| sue for patent infringement).  To "grant" such a patent license to a | ||||
| party means to make such an agreement or commitment not to enforce a | ||||
| patent against the party. | ||||
| 
 | ||||
|   If you convey a covered work, knowingly relying on a patent license, | ||||
| and the Corresponding Source of the work is not available for anyone | ||||
| to copy, free of charge and under the terms of this License, through a | ||||
| publicly available network server or other readily accessible means, | ||||
| then you must either (1) cause the Corresponding Source to be so | ||||
| available, or (2) arrange to deprive yourself of the benefit of the | ||||
| patent license for this particular work, or (3) arrange, in a manner | ||||
| consistent with the requirements of this License, to extend the patent | ||||
| license to downstream recipients.  "Knowingly relying" means you have | ||||
| actual knowledge that, but for the patent license, your conveying the | ||||
| covered work in a country, or your recipient's use of the covered work | ||||
| in a country, would infringe one or more identifiable patents in that | ||||
| country that you have reason to believe are valid. | ||||
| 
 | ||||
|   If, pursuant to or in connection with a single transaction or | ||||
| arrangement, you convey, or propagate by procuring conveyance of, a | ||||
| covered work, and grant a patent license to some of the parties | ||||
| receiving the covered work authorizing them to use, propagate, modify | ||||
| or convey a specific copy of the covered work, then the patent license | ||||
| you grant is automatically extended to all recipients of the covered | ||||
| work and works based on it. | ||||
| 
 | ||||
|   A patent license is "discriminatory" if it does not include within | ||||
| the scope of its coverage, prohibits the exercise of, or is | ||||
| conditioned on the non-exercise of one or more of the rights that are | ||||
| specifically granted under this License.  You may not convey a covered | ||||
| work if you are a party to an arrangement with a third party that is | ||||
| in the business of distributing software, under which you make payment | ||||
| to the third party based on the extent of your activity of conveying | ||||
| the work, and under which the third party grants, to any of the | ||||
| parties who would receive the covered work from you, a discriminatory | ||||
| patent license (a) in connection with copies of the covered work | ||||
| conveyed by you (or copies made from those copies), or (b) primarily | ||||
| for and in connection with specific products or compilations that | ||||
| contain the covered work, unless you entered into that arrangement, | ||||
| or that patent license was granted, prior to 28 March 2007. | ||||
| 
 | ||||
|   Nothing in this License shall be construed as excluding or limiting | ||||
| any implied license or other defenses to infringement that may | ||||
| otherwise be available to you under applicable patent law. | ||||
| 
 | ||||
|   12. No Surrender of Others' Freedom. | ||||
| 
 | ||||
|   If conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot convey a | ||||
| covered work so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you may | ||||
| not convey it at all.  For example, if you agree to terms that obligate you | ||||
| to collect a royalty for further conveying from those to whom you convey | ||||
| the Program, the only way you could satisfy both those terms and this | ||||
| License would be to refrain entirely from conveying the Program. | ||||
| 
 | ||||
|   13. Use with the GNU Affero General Public License. | ||||
| 
 | ||||
|   Notwithstanding any other provision of this License, you have | ||||
| permission to link or combine any covered work with a work licensed | ||||
| under version 3 of the GNU Affero General Public License into a single | ||||
| combined work, and to convey the resulting work.  The terms of this | ||||
| License will continue to apply to the part which is the covered work, | ||||
| but the special requirements of the GNU Affero General Public License, | ||||
| section 13, concerning interaction through a network will apply to the | ||||
| combination as such. | ||||
| 
 | ||||
|   14. Revised Versions of this License. | ||||
| 
 | ||||
|   The Free Software Foundation may publish revised and/or new versions of | ||||
| the GNU General Public License from time to time.  Such new versions will | ||||
| be similar in spirit to the present version, but may differ in detail to | ||||
| address new problems or concerns. | ||||
| 
 | ||||
|   Each version is given a distinguishing version number.  If the | ||||
| Program specifies that a certain numbered version of the GNU General | ||||
| Public License "or any later version" applies to it, you have the | ||||
| option of following the terms and conditions either of that numbered | ||||
| version or of any later version published by the Free Software | ||||
| Foundation.  If the Program does not specify a version number of the | ||||
| GNU General Public License, you may choose any version ever published | ||||
| by the Free Software Foundation. | ||||
| 
 | ||||
|   If the Program specifies that a proxy can decide which future | ||||
| versions of the GNU General Public License can be used, that proxy's | ||||
| public statement of acceptance of a version permanently authorizes you | ||||
| to choose that version for the Program. | ||||
| 
 | ||||
|   Later license versions may give you additional or different | ||||
| permissions.  However, no additional obligations are imposed on any | ||||
| author or copyright holder as a result of your choosing to follow a | ||||
| later version. | ||||
| 
 | ||||
|   15. Disclaimer of Warranty. | ||||
| 
 | ||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||||
| 
 | ||||
|   16. Limitation of Liability. | ||||
| 
 | ||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||||
| SUCH DAMAGES. | ||||
| 
 | ||||
|   17. Interpretation of Sections 15 and 16. | ||||
| 
 | ||||
|   If the disclaimer of warranty and limitation of liability provided | ||||
| above cannot be given local legal effect according to their terms, | ||||
| reviewing courts shall apply local law that most closely approximates | ||||
| an absolute waiver of all civil liability in connection with the | ||||
| Program, unless a warranty or assumption of liability accompanies a | ||||
| copy of the Program in return for a fee. | ||||
| 
 | ||||
|                      END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|             How to Apply These Terms to Your New Programs | ||||
| 
 | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| free software which everyone can redistribute and change under these terms. | ||||
| 
 | ||||
|   To do so, attach the following notices to the program.  It is safest | ||||
| to attach them to the start of each source file to most effectively | ||||
| state the exclusion of warranty; and each file should have at least | ||||
| the "copyright" line and a pointer to where the full notice is found. | ||||
| 
 | ||||
|     <one line to give the program's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
| 
 | ||||
|     This program is free software: you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation, either version 3 of the License, or | ||||
|     (at your option) any later version. | ||||
| 
 | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
| 
 | ||||
|   If the program does terminal interaction, make it output a short | ||||
| notice like this when it starts in an interactive mode: | ||||
| 
 | ||||
|     <program>  Copyright (C) <year>  <name of author> | ||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
| 
 | ||||
| The hypothetical commands `show w' and `show c' should show the appropriate | ||||
| parts of the General Public License.  Of course, your program's commands | ||||
| might be different; for a GUI interface, you would use an "about box". | ||||
| 
 | ||||
|   You should also get your employer (if you work as a programmer) or school, | ||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | ||||
| For more information on this, and how to apply and follow the GNU GPL, see | ||||
| <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
|   The GNU General Public License does not permit incorporating your program | ||||
| into proprietary programs.  If your program is a subroutine library, you | ||||
| may consider it more useful to permit linking proprietary applications with | ||||
| the library.  If this is what you want to do, use the GNU Lesser General | ||||
| Public License instead of this License.  But first, please read | ||||
| <https://www.gnu.org/licenses/why-not-lgpl.html>. | ||||
							
								
								
									
										61
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| # Bouquin | ||||
| 
 | ||||
| 
 | ||||
| ## Introduction | ||||
| 
 | ||||
| Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher. | ||||
| 
 | ||||
| It uses [SQLCipher bindings](https://pypi.org/project/sqlcipher3-wheels) as a drop-in replacement | ||||
| for SQLite3. This means that the underlying database for the notebook is encrypted at rest. | ||||
| 
 | ||||
| To increase security, the SQLCipher key is requested when the app is opened, and is not written | ||||
| to disk. | ||||
| 
 | ||||
| There is deliberately no network connectivity or syncing intended. | ||||
| 
 | ||||
| ## Screenshot | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
|  * Every 'page' is linked to the calendar day | ||||
|  * Basic markdown | ||||
|  * Automatic periodic saving (or explicitly save) | ||||
|  * Navigating from one day to the next automatically saves | ||||
|  * Basic keyboard shortcuts | ||||
|  * Transparent integrity checking of the database when it opens | ||||
| 
 | ||||
| 
 | ||||
| ## Yet to do | ||||
| 
 | ||||
|  * Search | ||||
|  * Taxonomy/tagging | ||||
|  * Ability to change the SQLCipher key | ||||
|  * Export to other formats (plaintext, json, sql etc) | ||||
| 
 | ||||
| 
 | ||||
| ## How to install | ||||
| 
 | ||||
| ### From source | ||||
| 
 | ||||
|  * Clone this repo or download the tarball from the releases page | ||||
|  * Ensure you have poetry installed | ||||
|  * Run `poetry install` to install dependencies | ||||
|  * Run `poetry run bouquin` to start the application. | ||||
| 
 | ||||
| ### From the releases page | ||||
| 
 | ||||
|  * Download the whl and run it | ||||
| 
 | ||||
| ### From PyPi | ||||
| 
 | ||||
|  * `pip install bouquin` | ||||
| 
 | ||||
| 
 | ||||
| ## How to run the tests | ||||
| 
 | ||||
|  * Clone the repo | ||||
|  * Ensure you have poetry installed | ||||
|  * Run `poetry install --with test` | ||||
|  * Run `poetry run pytest -vvv` | ||||
							
								
								
									
										1
									
								
								bouquin/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bouquin/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| from .main import main | ||||
							
								
								
									
										4
									
								
								bouquin/__main__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								bouquin/__main__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| from .main import main | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										92
									
								
								bouquin/db.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								bouquin/db.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from dataclasses import dataclass | ||||
| from pathlib import Path | ||||
| 
 | ||||
| from sqlcipher3 import dbapi2 as sqlite | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class DBConfig: | ||||
|     path: Path | ||||
|     key: str | ||||
| 
 | ||||
| 
 | ||||
| class DBManager: | ||||
|     def __init__(self, cfg: DBConfig): | ||||
|         self.cfg = cfg | ||||
|         self.conn: sqlite.Connection | None = None | ||||
| 
 | ||||
|     def connect(self) -> bool: | ||||
|         # Ensure parent dir exists | ||||
|         self.cfg.path.parent.mkdir(parents=True, exist_ok=True) | ||||
|         self.conn = sqlite.connect(str(self.cfg.path)) | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute(f"PRAGMA key = '{self.cfg.key}';") | ||||
|         cur.execute("PRAGMA cipher_compatibility = 4;") | ||||
|         cur.execute("PRAGMA journal_mode = WAL;") | ||||
|         self.conn.commit() | ||||
|         try: | ||||
|             self._integrity_ok() | ||||
|         except Exception: | ||||
|             self.conn.close() | ||||
|             self.conn = None | ||||
|             return False | ||||
|         self._ensure_schema() | ||||
|         return True | ||||
| 
 | ||||
|     def _integrity_ok(self) -> bool: | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute("PRAGMA cipher_integrity_check;") | ||||
|         rows = cur.fetchall() | ||||
| 
 | ||||
|         # OK | ||||
|         if not rows: | ||||
|             return | ||||
| 
 | ||||
|         # Not OK | ||||
|         details = "; ".join(str(r[0]) for r in rows if r and r[0] is not None) | ||||
|         raise sqlite.IntegrityError( | ||||
|             "SQLCipher integrity check failed" | ||||
|             + (f": {details}" if details else f" ({len(rows)} issue(s) reported)") | ||||
|         ) | ||||
| 
 | ||||
|     def _ensure_schema(self) -> None: | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute( | ||||
|             """ | ||||
|             CREATE TABLE IF NOT EXISTS entries ( | ||||
|                 date TEXT PRIMARY KEY, -- ISO yyyy-MM-dd | ||||
|                 content TEXT NOT NULL | ||||
|             ); | ||||
|             """ | ||||
|         ) | ||||
|         cur.execute("PRAGMA user_version = 1;") | ||||
|         self.conn.commit() | ||||
| 
 | ||||
|     def get_entry(self, date_iso: str) -> str: | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute("SELECT content FROM entries WHERE date = ?;", (date_iso,)) | ||||
|         row = cur.fetchone() | ||||
|         return row[0] if row else "" | ||||
| 
 | ||||
|     def upsert_entry(self, date_iso: str, content: str) -> None: | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute( | ||||
|             """ | ||||
|             INSERT INTO entries(date, content) VALUES(?, ?) | ||||
|             ON CONFLICT(date) DO UPDATE SET content = excluded.content; | ||||
|             """, | ||||
|             (date_iso, content), | ||||
|         ) | ||||
|         self.conn.commit() | ||||
| 
 | ||||
|     def dates_with_content(self) -> list[str]: | ||||
|         cur = self.conn.cursor() | ||||
|         cur.execute("SELECT date FROM entries WHERE TRIM(content) <> '';") | ||||
|         return [r[0] for r in cur.fetchall()] | ||||
| 
 | ||||
|     def close(self) -> None: | ||||
|         if self.conn is not None: | ||||
|             self.conn.close() | ||||
|             self.conn = None | ||||
							
								
								
									
										112
									
								
								bouquin/highlighter.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								bouquin/highlighter.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import re | ||||
| from PySide6.QtGui import QFont, QTextCharFormat, QSyntaxHighlighter, QColor | ||||
| 
 | ||||
| 
 | ||||
| class MarkdownHighlighter(QSyntaxHighlighter): | ||||
|     ST_NORMAL = 0 | ||||
|     ST_CODE = 1 | ||||
| 
 | ||||
|     FENCE = re.compile(r"^```") | ||||
| 
 | ||||
|     def __init__(self, document): | ||||
|         super().__init__(document) | ||||
| 
 | ||||
|         base_size = document.defaultFont().pointSizeF() or 12.0 | ||||
| 
 | ||||
|         # Monospace for code | ||||
|         self.mono = QFont("Monospace") | ||||
|         self.mono.setStyleHint(QFont.TypeWriter) | ||||
| 
 | ||||
|         # Light, high-contrast scheme for code | ||||
|         self.col_bg = QColor("#eef2f6")  # light code bg | ||||
|         self.col_fg = QColor("#1f2328")  # dark text | ||||
| 
 | ||||
|         # Formats | ||||
|         self.fmt_h = [QTextCharFormat() for _ in range(6)] | ||||
|         for i, f in enumerate(self.fmt_h, start=1): | ||||
|             f.setFontWeight(QFont.Weight.Bold) | ||||
|             f.setFontPointSize(base_size + (7 - i)) | ||||
|         self.fmt_bold = QTextCharFormat() | ||||
|         self.fmt_bold.setFontWeight(QFont.Weight.Bold) | ||||
|         self.fmt_italic = QTextCharFormat() | ||||
|         self.fmt_italic.setFontItalic(True) | ||||
|         self.fmt_quote = QTextCharFormat() | ||||
|         self.fmt_quote.setForeground(QColor("#6a737d")) | ||||
|         self.fmt_link = QTextCharFormat() | ||||
|         self.fmt_link.setFontUnderline(True) | ||||
|         self.fmt_list = QTextCharFormat() | ||||
|         self.fmt_list.setFontWeight(QFont.Weight.DemiBold) | ||||
|         self.fmt_strike = QTextCharFormat() | ||||
|         self.fmt_strike.setFontStrikeOut(True) | ||||
| 
 | ||||
|         # Uniform code style | ||||
|         self.fmt_code = QTextCharFormat() | ||||
|         self.fmt_code.setFont(self.mono) | ||||
|         self.fmt_code.setFontPointSize(max(6.0, base_size - 1)) | ||||
|         self.fmt_code.setBackground(self.col_bg) | ||||
|         self.fmt_code.setForeground(self.col_fg) | ||||
| 
 | ||||
|         # Simple patterns | ||||
|         self.re_heading = re.compile(r"^(#{1,6}) +.*$") | ||||
|         self.re_bold = re.compile(r"\*\*(.+?)\*\*|__(.+?)__") | ||||
|         self.re_italic = re.compile(r"\*(?!\*)(.+?)\*|_(?!_)(.+?)_") | ||||
|         self.re_strike = re.compile(r"~~(.+?)~~") | ||||
|         self.re_inline_code = re.compile(r"`([^`]+)`") | ||||
|         self.re_link = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") | ||||
|         self.re_list = re.compile(r"^ *(?:[-*+] +|[0-9]+[.)] +)") | ||||
|         self.re_quote = re.compile(r"^> ?.*$") | ||||
| 
 | ||||
|     def highlightBlock(self, text: str) -> None: | ||||
|         prev = self.previousBlockState() | ||||
|         in_code = prev == self.ST_CODE | ||||
| 
 | ||||
|         if in_code: | ||||
|             # Entire line is code | ||||
|             self.setFormat(0, len(text), self.fmt_code) | ||||
|             if self.FENCE.match(text): | ||||
|                 self.setCurrentBlockState(self.ST_NORMAL) | ||||
|             else: | ||||
|                 self.setCurrentBlockState(self.ST_CODE) | ||||
|             return | ||||
| 
 | ||||
|         # Starting/ending a fenced block? | ||||
|         if self.FENCE.match(text): | ||||
|             self.setFormat(0, len(text), self.fmt_code) | ||||
|             self.setCurrentBlockState(self.ST_CODE) | ||||
|             return | ||||
| 
 | ||||
|         # --- Normal markdown styling --- | ||||
|         m = self.re_heading.match(text) | ||||
|         if m: | ||||
|             level = min(len(m.group(1)), 6) | ||||
|             self.setFormat(0, len(text), self.fmt_h[level - 1]) | ||||
|             self.setCurrentBlockState(self.ST_NORMAL) | ||||
|             return | ||||
| 
 | ||||
|         m = self.re_list.match(text) | ||||
|         if m: | ||||
|             self.setFormat(m.start(), m.end() - m.start(), self.fmt_list) | ||||
| 
 | ||||
|         if self.re_quote.match(text): | ||||
|             self.setFormat(0, len(text), self.fmt_quote) | ||||
| 
 | ||||
|         for m in self.re_inline_code.finditer(text): | ||||
|             self.setFormat(m.start(), m.end() - m.start(), self.fmt_code) | ||||
| 
 | ||||
|         for m in self.re_bold.finditer(text): | ||||
|             self.setFormat(m.start(), m.end() - m.start(), self.fmt_bold) | ||||
| 
 | ||||
|         for m in self.re_italic.finditer(text): | ||||
|             self.setFormat(m.start(), m.end() - m.start(), self.fmt_italic) | ||||
| 
 | ||||
|         for m in self.re_strike.finditer(text): | ||||
|             self.setFormat(m.start(), m.end() - m.start(), self.fmt_strike) | ||||
| 
 | ||||
|         for m in self.re_link.finditer(text): | ||||
|             start = m.start(1) - 1 | ||||
|             length = len(m.group(1)) + 2 | ||||
|             self.setFormat(start, length, self.fmt_link) | ||||
| 
 | ||||
|         self.setCurrentBlockState(self.ST_NORMAL) | ||||
							
								
								
									
										41
									
								
								bouquin/key_prompt.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								bouquin/key_prompt.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from PySide6.QtWidgets import ( | ||||
|     QDialog, | ||||
|     QVBoxLayout, | ||||
|     QLabel, | ||||
|     QLineEdit, | ||||
|     QPushButton, | ||||
|     QDialogButtonBox, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class KeyPrompt(QDialog): | ||||
|     def __init__( | ||||
|         self, | ||||
|         parent=None, | ||||
|         title: str = "Unlock database", | ||||
|         message: str = "Enter SQLCipher key", | ||||
|     ): | ||||
|         super().__init__(parent) | ||||
|         self.setWindowTitle(title) | ||||
|         v = QVBoxLayout(self) | ||||
|         v.addWidget(QLabel(message)) | ||||
|         self.edit = QLineEdit() | ||||
|         self.edit.setEchoMode(QLineEdit.Password) | ||||
|         v.addWidget(self.edit) | ||||
|         toggle = QPushButton("Show") | ||||
|         toggle.setCheckable(True) | ||||
|         toggle.toggled.connect( | ||||
|             lambda c: self.edit.setEchoMode( | ||||
|                 QLineEdit.Normal if c else QLineEdit.Password | ||||
|             ) | ||||
|         ) | ||||
|         v.addWidget(toggle) | ||||
|         bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) | ||||
|         bb.accepted.connect(self.accept) | ||||
|         bb.rejected.connect(self.reject) | ||||
|         v.addWidget(bb) | ||||
| 
 | ||||
|     def key(self) -> str: | ||||
|         return self.edit.text() | ||||
							
								
								
									
										15
									
								
								bouquin/main.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bouquin/main.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import sys | ||||
| from PySide6.QtWidgets import QApplication | ||||
| 
 | ||||
| from .settings import APP_NAME, APP_ORG | ||||
| from .main_window import MainWindow | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     app = QApplication(sys.argv) | ||||
|     app.setApplicationName(APP_NAME) | ||||
|     app.setOrganizationName(APP_ORG) | ||||
|     win = MainWindow(); win.show() | ||||
|     sys.exit(app.exec()) | ||||
							
								
								
									
										245
									
								
								bouquin/main_window.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								bouquin/main_window.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,245 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import sys | ||||
| 
 | ||||
| from PySide6.QtCore import QDate, QTimer, Qt | ||||
| from PySide6.QtGui import QAction, QFont, QTextCharFormat | ||||
| from PySide6.QtWidgets import ( | ||||
|     QDialog, | ||||
|     QCalendarWidget, | ||||
|     QMainWindow, | ||||
|     QMessageBox, | ||||
|     QPlainTextEdit, | ||||
|     QSplitter, | ||||
|     QVBoxLayout, | ||||
|     QWidget, | ||||
|     QSizePolicy, | ||||
| ) | ||||
| 
 | ||||
| from .db import DBManager | ||||
| from .settings import APP_NAME, load_db_config, save_db_config | ||||
| from .key_prompt import KeyPrompt | ||||
| from .highlighter import MarkdownHighlighter | ||||
| from .settings_dialog import SettingsDialog | ||||
| 
 | ||||
| 
 | ||||
| class MainWindow(QMainWindow): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         self.setWindowTitle(APP_NAME) | ||||
|         self.setMinimumSize(1000, 650) | ||||
| 
 | ||||
|         self.cfg = load_db_config() | ||||
|         # Always prompt for the key (we never store it) | ||||
|         if not self._prompt_for_key_until_valid(): | ||||
|             sys.exit(1) | ||||
| 
 | ||||
|         # ---- UI: Left fixed panel (calendar) + right editor ----------------- | ||||
|         self.calendar = QCalendarWidget() | ||||
|         self.calendar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | ||||
|         self.calendar.setGridVisible(True) | ||||
|         self.calendar.selectionChanged.connect(self._on_date_changed) | ||||
| 
 | ||||
|         left_panel = QWidget() | ||||
|         left_layout = QVBoxLayout(left_panel) | ||||
|         left_layout.setContentsMargins(8, 8, 8, 8) | ||||
|         left_layout.addWidget(self.calendar, alignment=Qt.AlignTop) | ||||
|         left_layout.addStretch(1) | ||||
|         left_panel.setFixedWidth(self.calendar.sizeHint().width() + 16) | ||||
| 
 | ||||
|         self.editor = QPlainTextEdit() | ||||
|         tab_w = 4 * self.editor.fontMetrics().horizontalAdvance(" ") | ||||
|         self.editor.setTabStopDistance(tab_w) | ||||
|         self.highlighter = MarkdownHighlighter(self.editor.document()) | ||||
| 
 | ||||
|         split = QSplitter() | ||||
|         split.addWidget(left_panel) | ||||
|         split.addWidget(self.editor) | ||||
|         split.setStretchFactor(1, 1)  # editor grows | ||||
| 
 | ||||
|         container = QWidget() | ||||
|         lay = QVBoxLayout(container) | ||||
|         lay.addWidget(split) | ||||
|         self.setCentralWidget(container) | ||||
| 
 | ||||
|         # Status bar for feedback | ||||
|         self.statusBar().showMessage("Ready", 800) | ||||
| 
 | ||||
|         # Menu bar (File) | ||||
|         mb = self.menuBar() | ||||
|         file_menu = mb.addMenu("&File") | ||||
|         act_save = QAction("&Save", self) | ||||
|         act_save.setShortcut("Ctrl+S") | ||||
|         act_save.triggered.connect(lambda: self._save_current(explicit=True)) | ||||
|         file_menu.addAction(act_save) | ||||
|         act_settings = QAction("&Settings", self) | ||||
|         act_settings.triggered.connect(self._open_settings) | ||||
|         file_menu.addAction(act_settings) | ||||
|         file_menu.addSeparator() | ||||
|         act_quit = QAction("&Quit", self) | ||||
|         act_quit.setShortcut("Ctrl+Q") | ||||
|         act_quit.triggered.connect(self.close) | ||||
|         file_menu.addAction(act_quit) | ||||
| 
 | ||||
|         # Navigate menu with next/previous day | ||||
|         nav_menu = mb.addMenu("&Navigate") | ||||
|         act_prev = QAction("Previous Day", self) | ||||
|         act_prev.setShortcut("Ctrl+P") | ||||
|         act_prev.setShortcutContext(Qt.ApplicationShortcut) | ||||
|         act_prev.triggered.connect(lambda: self._adjust_day(-1)) | ||||
|         nav_menu.addAction(act_prev) | ||||
|         self.addAction(act_prev) | ||||
| 
 | ||||
|         act_next = QAction("Next Day", self) | ||||
|         act_next.setShortcut("Ctrl+N") | ||||
|         act_next.setShortcutContext(Qt.ApplicationShortcut) | ||||
|         act_next.triggered.connect(lambda: self._adjust_day(1)) | ||||
|         nav_menu.addAction(act_next) | ||||
|         self.addAction(act_next) | ||||
| 
 | ||||
|         # Autosave | ||||
|         self._dirty = False | ||||
|         self._save_timer = QTimer(self) | ||||
|         self._save_timer.setSingleShot(True) | ||||
|         self._save_timer.timeout.connect(self._save_current) | ||||
|         self.editor.textChanged.connect(self._on_text_changed) | ||||
| 
 | ||||
|         # First load + mark dates with content | ||||
|         self._load_selected_date() | ||||
|         self._refresh_calendar_marks() | ||||
| 
 | ||||
|     # --- DB lifecycle | ||||
|     def _try_connect(self) -> bool: | ||||
|         try: | ||||
|             self.db = DBManager(self.cfg) | ||||
|             ok = self.db.connect() | ||||
|         except Exception as e: | ||||
|             if str(e) == "file is not a database": | ||||
|                 error = "The key is probably incorrect." | ||||
|             else: | ||||
|                 error = str(e) | ||||
|             QMessageBox.critical(self, "Database Error", error) | ||||
|             return False | ||||
|         return ok | ||||
| 
 | ||||
|     def _prompt_for_key_until_valid(self) -> bool: | ||||
|         while True: | ||||
|             dlg = KeyPrompt(self, message="Enter a key to unlock the notebook") | ||||
|             if dlg.exec() != QDialog.Accepted: | ||||
|                 return False | ||||
|             self.cfg.key = dlg.key() | ||||
|             if self._try_connect(): | ||||
|                 return True | ||||
| 
 | ||||
|     # --- Calendar marks to indicate text exists for htat day ----------------- | ||||
|     def _refresh_calendar_marks(self): | ||||
|         fmt_bold = QTextCharFormat() | ||||
|         fmt_bold.setFontWeight(QFont.Weight.Bold) | ||||
|         # Clear previous marks | ||||
|         for d in getattr(self, "_marked_dates", set()): | ||||
|             self.calendar.setDateTextFormat(d, QTextCharFormat()) | ||||
|         self._marked_dates = set() | ||||
|         try: | ||||
|             for date_iso in self.db.dates_with_content(): | ||||
|                 qd = QDate.fromString(date_iso, "yyyy-MM-dd") | ||||
|                 if qd.isValid(): | ||||
|                     self.calendar.setDateTextFormat(qd, fmt_bold) | ||||
|                     self._marked_dates.add(qd) | ||||
|         except Exception: | ||||
|             pass | ||||
| 
 | ||||
|     # --- UI handlers --------------------------------------------------------- | ||||
|     def _current_date_iso(self) -> str: | ||||
|         d = self.calendar.selectedDate() | ||||
|         return f"{d.year():04d}-{d.month():02d}-{d.day():02d}" | ||||
| 
 | ||||
|     def _load_selected_date(self): | ||||
|         date_iso = self._current_date_iso() | ||||
|         try: | ||||
|             text = self.db.get_entry(date_iso) | ||||
|         except Exception as e: | ||||
|             QMessageBox.critical(self, "Read Error", str(e)) | ||||
|             return | ||||
|         self.editor.blockSignals(True) | ||||
|         self.editor.setPlainText(text) | ||||
|         self.editor.blockSignals(False) | ||||
|         self._dirty = False | ||||
|         # track which date the editor currently represents | ||||
|         self._active_date_iso = date_iso | ||||
| 
 | ||||
|     def _on_text_changed(self): | ||||
|         self._dirty = True | ||||
|         self._save_timer.start(1200)  # autosave after idle | ||||
| 
 | ||||
|     def _adjust_day(self, delta: int): | ||||
|         """Move selection by delta days (negative for previous).""" | ||||
|         d = self.calendar.selectedDate().addDays(delta) | ||||
|         self.calendar.setSelectedDate(d) | ||||
| 
 | ||||
|     def _on_date_changed(self): | ||||
|         """ | ||||
|         When the calendar selection changes, save the previous day's note if dirty, | ||||
|         so we don't lose that text, then load the newly selected day. | ||||
|         """ | ||||
|         # Stop pending autosave and persist current buffer if needed | ||||
|         try: | ||||
|             self._save_timer.stop() | ||||
|         except Exception: | ||||
|             pass | ||||
|         prev = getattr(self, "_active_date_iso", None) | ||||
|         if prev and self._dirty: | ||||
|             self._save_date(prev, explicit=False) | ||||
|         # Now load the newly selected date | ||||
|         self._load_selected_date() | ||||
| 
 | ||||
|     def _save_date(self, date_iso: str, explicit: bool = False): | ||||
|         """ | ||||
|         Save editor contents into the given date. Shows status on success. | ||||
|         explicit=True means user invoked Save: show feedback even if nothing changed. | ||||
|         """ | ||||
|         if not self._dirty and not explicit: | ||||
|             return | ||||
|         text = self.editor.toPlainText() | ||||
|         try: | ||||
|             self.db.upsert_entry(date_iso, text) | ||||
|         except Exception as e: | ||||
|             QMessageBox.critical(self, "Save Error", str(e)) | ||||
|             return | ||||
|         self._dirty = False | ||||
|         self._refresh_calendar_marks() | ||||
|         # Feedback in the status bar | ||||
|         from datetime import datetime as _dt | ||||
| 
 | ||||
|         self.statusBar().showMessage( | ||||
|             f"Saved {date_iso} at {_dt.now().strftime('%H:%M:%S')}", 2000 | ||||
|         ) | ||||
| 
 | ||||
|     def _save_current(self, explicit: bool = False): | ||||
|         # Delegate to _save_date for the currently selected date | ||||
|         self._save_date(self._current_date_iso(), explicit) | ||||
| 
 | ||||
|     def _open_settings(self): | ||||
|         dlg = SettingsDialog(self.cfg, self) | ||||
|         if dlg.exec() == QDialog.Accepted: | ||||
|             new_cfg = dlg.config | ||||
|             if new_cfg.path != self.cfg.path: | ||||
|                 # Save the new path to the notebook | ||||
|                 self.cfg.path = new_cfg.path | ||||
|                 save_db_config(self.cfg) | ||||
|                 self.db.close() | ||||
|                 # Prompt again for the key for the new path | ||||
|                 if not self._prompt_for_key_until_valid(): | ||||
|                     QMessageBox.warning( | ||||
|                         self, "Reopen failed", "Could not unlock database at new path." | ||||
|                     ) | ||||
|                     return | ||||
|                 self._load_selected_date() | ||||
|                 self._refresh_calendar_marks() | ||||
| 
 | ||||
|     def closeEvent(self, event):  # noqa: N802 | ||||
|         try: | ||||
|             self._save_current() | ||||
|             self.db.close() | ||||
|         except Exception: | ||||
|             pass | ||||
|         super().closeEvent(event) | ||||
							
								
								
									
										29
									
								
								bouquin/settings.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								bouquin/settings.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from pathlib import Path | ||||
| from PySide6.QtCore import QSettings, QStandardPaths | ||||
| 
 | ||||
| from .db import DBConfig | ||||
| 
 | ||||
| APP_ORG = "Bouquin" | ||||
| APP_NAME = "Bouquin" | ||||
| 
 | ||||
| 
 | ||||
| def default_db_path() -> Path: | ||||
|     base = Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)) | ||||
|     return base / "notebook.db" | ||||
| 
 | ||||
| 
 | ||||
| def get_settings() -> QSettings: | ||||
|     return QSettings(APP_ORG, APP_NAME) | ||||
| 
 | ||||
| 
 | ||||
| def load_db_config() -> DBConfig: | ||||
|     s = get_settings() | ||||
|     path = Path(s.value("db/path", str(default_db_path()))) | ||||
|     return DBConfig(path=path, key="") | ||||
| 
 | ||||
| 
 | ||||
| def save_db_config(cfg: DBConfig) -> None: | ||||
|     s = get_settings() | ||||
|     s.setValue("db/path", str(cfg.path)) | ||||
							
								
								
									
										72
									
								
								bouquin/settings_dialog.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								bouquin/settings_dialog.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from pathlib import Path | ||||
| 
 | ||||
| from PySide6.QtWidgets import ( | ||||
|     QDialog, | ||||
|     QFormLayout, | ||||
|     QHBoxLayout, | ||||
|     QVBoxLayout, | ||||
|     QWidget, | ||||
|     QLineEdit, | ||||
|     QPushButton, | ||||
|     QFileDialog, | ||||
|     QDialogButtonBox, | ||||
|     QSizePolicy, | ||||
| ) | ||||
| 
 | ||||
| from .db import DBConfig | ||||
| from .settings import save_db_config | ||||
| 
 | ||||
| 
 | ||||
| class SettingsDialog(QDialog): | ||||
|     def __init__(self, cfg: DBConfig, parent=None): | ||||
|         super().__init__(parent) | ||||
|         self.setWindowTitle("Settings") | ||||
|         self._cfg = DBConfig(path=cfg.path, key="") | ||||
| 
 | ||||
|         form = QFormLayout() | ||||
|         form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) | ||||
|         self.setMinimumWidth(520) | ||||
|         self.setSizeGripEnabled(True) | ||||
| 
 | ||||
|         self.path_edit = QLineEdit(str(self._cfg.path)) | ||||
|         self.path_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) | ||||
|         browse_btn = QPushButton("Browse…") | ||||
|         browse_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | ||||
|         browse_btn.clicked.connect(self._browse) | ||||
|         path_row = QWidget() | ||||
|         h = QHBoxLayout(path_row) | ||||
|         h.setContentsMargins(0, 0, 0, 0) | ||||
|         h.addWidget(self.path_edit, 1) | ||||
|         h.addWidget(browse_btn, 0) | ||||
|         h.setStretch(0, 1) | ||||
|         h.setStretch(1, 0) | ||||
|         form.addRow("Database path", path_row) | ||||
| 
 | ||||
|         bb = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) | ||||
|         bb.accepted.connect(self._save) | ||||
|         bb.rejected.connect(self.reject) | ||||
| 
 | ||||
|         v = QVBoxLayout(self) | ||||
|         v.addLayout(form) | ||||
|         v.addWidget(bb) | ||||
| 
 | ||||
|     def _browse(self): | ||||
|         p, _ = QFileDialog.getSaveFileName( | ||||
|             self, | ||||
|             "Choose database file", | ||||
|             self.path_edit.text(), | ||||
|             "DB Files (*.db);;All Files (*)", | ||||
|         ) | ||||
|         if p: | ||||
|             self.path_edit.setText(p) | ||||
| 
 | ||||
|     def _save(self): | ||||
|         self._cfg = DBConfig(path=Path(self.path_edit.text()), key="") | ||||
|         save_db_config(self._cfg) | ||||
|         self.accept() | ||||
| 
 | ||||
|     @property | ||||
|     def config(self) -> DBConfig: | ||||
|         return self._cfg | ||||
							
								
								
									
										544
									
								
								poetry.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								poetry.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,544 @@ | |||
| # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. | ||||
| 
 | ||||
| [[package]] | ||||
| name = "colorama" | ||||
| version = "0.4.6" | ||||
| description = "Cross-platform colored terminal text." | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" | ||||
| files = [ | ||||
|     {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, | ||||
|     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "coverage" | ||||
| version = "7.10.7" | ||||
| description = "Code coverage measurement for Python" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, | ||||
|     {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, | ||||
|     {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, | ||||
|     {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, | ||||
|     {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, | ||||
|     {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, | ||||
|     {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, | ||||
|     {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, | ||||
|     {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} | ||||
| 
 | ||||
| [package.extras] | ||||
| toml = ["tomli"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "exceptiongroup" | ||||
| version = "1.3.0" | ||||
| description = "Backport of PEP 654 (exception groups)" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, | ||||
|     {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} | ||||
| 
 | ||||
| [package.extras] | ||||
| test = ["pytest (>=6)"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "iniconfig" | ||||
| version = "2.1.0" | ||||
| description = "brain-dead simple config-ini parsing" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, | ||||
|     {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "packaging" | ||||
| version = "25.0" | ||||
| description = "Core utilities for Python packages" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, | ||||
|     {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pluggy" | ||||
| version = "1.6.0" | ||||
| description = "plugin and hook calling mechanisms for python" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, | ||||
|     {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, | ||||
| ] | ||||
| 
 | ||||
| [package.extras] | ||||
| dev = ["pre-commit", "tox"] | ||||
| testing = ["coverage", "pytest", "pytest-benchmark"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pygments" | ||||
| version = "2.19.2" | ||||
| description = "Pygments is a syntax highlighting package written in Python." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, | ||||
|     {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, | ||||
| ] | ||||
| 
 | ||||
| [package.extras] | ||||
| windows-terminal = ["colorama (>=0.4.6)"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pyside6" | ||||
| version = "6.10.0" | ||||
| description = "Python bindings for the Qt cross-platform application and UI framework" | ||||
| optional = false | ||||
| python-versions = "<3.14,>=3.9" | ||||
| files = [ | ||||
|     {file = "pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0"}, | ||||
|     {file = "pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01"}, | ||||
|     {file = "pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820"}, | ||||
|     {file = "pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f"}, | ||||
|     {file = "pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| PySide6_Addons = "6.10.0" | ||||
| PySide6_Essentials = "6.10.0" | ||||
| shiboken6 = "6.10.0" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pyside6-addons" | ||||
| version = "6.10.0" | ||||
| description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" | ||||
| optional = false | ||||
| python-versions = "<3.14,>=3.9" | ||||
| files = [ | ||||
|     {file = "pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c"}, | ||||
|     {file = "pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464"}, | ||||
|     {file = "pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0"}, | ||||
|     {file = "pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859"}, | ||||
|     {file = "pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| PySide6_Essentials = "6.10.0" | ||||
| shiboken6 = "6.10.0" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pyside6-essentials" | ||||
| version = "6.10.0" | ||||
| description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" | ||||
| optional = false | ||||
| python-versions = "<3.14,>=3.9" | ||||
| files = [ | ||||
|     {file = "pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a"}, | ||||
|     {file = "pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8"}, | ||||
|     {file = "pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998"}, | ||||
|     {file = "pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe"}, | ||||
|     {file = "pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| shiboken6 = "6.10.0" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pytest" | ||||
| version = "8.4.2" | ||||
| description = "pytest: simple powerful testing with Python" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, | ||||
|     {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} | ||||
| exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} | ||||
| iniconfig = ">=1" | ||||
| packaging = ">=20" | ||||
| pluggy = ">=1.5,<2" | ||||
| pygments = ">=2.7.2" | ||||
| tomli = {version = ">=1", markers = "python_version < \"3.11\""} | ||||
| 
 | ||||
| [package.extras] | ||||
| dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pytest-cov" | ||||
| version = "7.0.0" | ||||
| description = "Pytest plugin for measuring coverage." | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, | ||||
|     {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| coverage = {version = ">=7.10.6", extras = ["toml"]} | ||||
| pluggy = ">=1.2" | ||||
| pytest = ">=7" | ||||
| 
 | ||||
| [package.extras] | ||||
| testing = ["process-tests", "pytest-xdist", "virtualenv"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pytest-mock" | ||||
| version = "3.15.1" | ||||
| description = "Thin-wrapper around the mock package for easier use with pytest" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, | ||||
|     {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| pytest = ">=6.2.5" | ||||
| 
 | ||||
| [package.extras] | ||||
| dev = ["pre-commit", "pytest-asyncio", "tox"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pytest-qt" | ||||
| version = "4.5.0" | ||||
| description = "pytest support for PyQt and PySide applications" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff"}, | ||||
|     {file = "pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f"}, | ||||
| ] | ||||
| 
 | ||||
| [package.dependencies] | ||||
| pluggy = ">=1.1" | ||||
| pytest = "*" | ||||
| typing_extensions = "*" | ||||
| 
 | ||||
| [package.extras] | ||||
| dev = ["pre-commit", "tox"] | ||||
| doc = ["sphinx", "sphinx_rtd_theme"] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "shiboken6" | ||||
| version = "6.10.0" | ||||
| description = "Python/C++ bindings helper module" | ||||
| optional = false | ||||
| python-versions = "<3.14,>=3.9" | ||||
| files = [ | ||||
|     {file = "shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543"}, | ||||
|     {file = "shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234"}, | ||||
|     {file = "shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148"}, | ||||
|     {file = "shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717"}, | ||||
|     {file = "shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "sqlcipher3-wheels" | ||||
| version = "0.5.5.post0" | ||||
| description = "DB-API 2.0 interface for SQLCipher 3.x" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:836cff85673ab9bdfe0f3e2bc38aefddb5f3a4c0de397b92f83546bb94ea38aa"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3fde9076a8810d19044f65fdfeeee5a9d044176ce91adc2258c8b18cb945474"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ad3ccb27f3fc9260b1bcebfd33fc5af1c2a1bf6a50e8e1bf7991d492458b438"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94bb8ab8cf7ae3dc0d51dcb75bf242ae4bd2f18549bfc975fd696c181e9ea8ca"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0bf0a169b480615ea2021e7266e1154990762216d1fd8105b93d1fee336f49"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79cc1af145345e9bd625c961e4efc8fc6c6eefcaec90fbcf1c6b981492c08031"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d8b9f1c6d283acc5a0da16574c0f7690ba5b14cb5935f3078ccf8404a530075"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952a23069a149a192a5eb8a9e552772b38c012825238175bc810f445a3aa8000"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24f1a57a4aa18d9ecd38cfce69dd06e58cfb521151a8316e18183e603e7108f4"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6568c64adc55f9882ba36c11a446810bd5d4c03796aab8ecb9024f3bca9eb2cd"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:26c2b58d2f2a9dd23ad4c310fb6c0f0c82ca4f36a0d4177a70f0efeb332798ee"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46827ffc7e705c5ecdf23ec69f56dd55b20857dc3c3c4893e360de8a38b4e708"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4033bbe2f0342936736ce7b8b2626f532509315576d5376764b410deae181cad"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win32.whl", hash = "sha256:bfb26dbba945860427bd3f82c132e6d2ef409baa062d315b952dd5a930b25870"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win_amd64.whl", hash = "sha256:168270b8fb295314aa4ee9f74435ceee42207bd16fe908f646a829b3a9daedad"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win_arm64.whl", hash = "sha256:1f1bb2c4c6defa812eb0238055a283cf3c2f400e956a57c25cf65cbdbac6783f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55d557376a90f14baf0f35e917f8644c3a8cf48897947fcd7ecf51d490dd689f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1739264a971901088fe1670befb8a8a707543186c8eecc58158ca287e309b2"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:920e19345b6c5335b61e9fbed2625f96dbc3b0269ab5120baeae2c9288f0be01"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462734f6d0703f863f5968419d229de75bbf2a829f762bfb257b6df2355f977"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c397305b65612da76f254c692ff866571aa98fd3817ed0e40fce6d568d704966"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf3467fe834075b58215c50f9db7355ef86a73d256ac8ba5fffb8c946741a5dc"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a76d783c095a3c95185757c418e3bad3eab69cbf986970d422cce5431e84d7f5"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf8d78895ee0f04dc525942a1f40796fa7c3d7d7fb36c987f55c243ce34192d"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d583a10dbe9a1752968788c2d6438461ec7068608ceaa72e6468d80727c3152e"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8c3156b39bb8f24dfbe17a49126d8fa404b00c01d7aa84e64a2293db1dae1a38"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:15c3cf77b31973aa008174518fa439d8620a093964c2d9edcb8d23d543345839"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f5743db0f3492359c2ab3a56b6bed00ecba193f2c75c74e8e3d78a45f1eb7c95"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40a213a3633f19c96432304a16f0cff7c4aeca1a3d2042d4be36e576e64a70"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win32.whl", hash = "sha256:433456ce962ae50887d6428d55bad46e5748a2cdd3d036180eb0bcdbe8bae9f9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win_amd64.whl", hash = "sha256:ca4332b1890cc4f80587be8bd529e20475bd3291e07f11115b1fc773947b264a"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win_arm64.whl", hash = "sha256:a4634300cb2440baf17a78d6481d10902de4a2a6518f83a5ab2fe081e6b20b42"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8df43c11d767c6aac5cc300c1957356a9fd1b25f1946891003cf20a0146241"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:797653f08ecffcef2948dfd907fb20dab402d9efde6217c96befafc236e96c5b"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dca428fde0d1a522473f766465324d6539d324f219f4f7c159a3eb0d4f9983c5"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e97922240a04b44637eabf39f86d243fe61fe7db1bd2ad219eb4053158f263"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8d3a366e52a6732b1ccff14f9ca77ecbee53abfce87c417bf05d4301484584f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dce28a2431260251d7acf253ea1950983e48dfec64245126b39a770d5a88f507"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea64cce27152cae453c353038336bda0dc1f885e5e8e30b5cd28b8c9b498bbeb"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02d9e6120a496f083c525efc34408d4f2ca282da05bebcc967a0aa1e12a0d6ca"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:875cfc61bbf694b8327c2485e5ed40573e8b715f4e583502f12c51c8d5a92dd5"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0e9ed3ff9c00ba3888f8dbc0c7c84377ef66f21c5f4ac373fc690dcf5e9bd594"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08ad6d767502429e497b6d03b5ae11e43e896d36f05ac8e60c12d8f124378bc1"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ecdefdcf7ab8cb14b3147a59af83e8e3e5e3bed46fc43ab86a657f5c306a83d2"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75ffe5677407bf20a32486eb6facfbb07a353ce7c9aecc9fefd4e9d3275605d7"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win32.whl", hash = "sha256:97b6c6556b430b5c0dff53e8f709f90ba53294c2a3958a8c38f573c6dbf467d9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win_amd64.whl", hash = "sha256:248cae211991f1ffb3a885a1223e62abee70c6c208fc2224a8dbf73d4e825baa"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win_arm64.whl", hash = "sha256:5a49fc3a859a53fd025dc2fa08410292d267018897fc63198de6c92860fa8be7"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f798e1591fa5ba14d9da08a54f18e7000fd74973cde12eb862a3928a69b7996"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:369011b8dc68741313a8b77bb68a70b76052390eaf819e4cd6e13d0acbea602d"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5658990462a728c1f4b472d23c1f7f577eb2bced5bbbf7c2b45158b8340484bd"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907a166e4e563da67fe22c480244459512e32d3e00853b3f1e6fdb9da6aa2da6"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ba972405e9f16042e37cbcb4fef47248339c8410847390d41268bd45dc3f6ca"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b5f1b380fe3b869f701f9d2a8c09e9edfeec261573c8bb009a3336717260d65"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2978c9d964ad643b0bc61e19d8d608a515ff270e9a2f1f6c5aeb8ad56255def"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29457feb1516a2542aa7676e6d03bf913191690bf1ed6c82353782a380388508"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:021af568414741a45bfca41d682da64916a7873498a31d896cc34ad540939c6b"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7471d12eef489eea60cc3806bae0690f6d0733f7aea371a3ad5c5642f3bc04a9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f5faf683db9ade192a870e28b1eeeec2eb0aeca18e88fa52195a4639974c7cb"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:64fe6bac67d2b807b91102eef41d7f389e008ded80575ba597b454e05f9522e5"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:63ef1e23eb9729e79783e2ab4b19f64276c155ba0a85ba1eeb21e248c6ce0956"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win32.whl", hash = "sha256:4eafde00138dd3753085b4f5eab0811247207b699de862589f886a94ad3628a4"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win_amd64.whl", hash = "sha256:909864f275460646d0bf5475dc42e9c2cadd79cd40805ea32fe9a69300595301"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win_arm64.whl", hash = "sha256:a831846cc6b01d7f99576efbf797b61a269dffa6885f530b6957573ce1a24f10"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:aaad03f3eb099401306fead806908c85b923064a9da7a99c33a72c3b7c9143bf"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74d4b4cde184d9e354152fd1867bcbaee468529865703ad863840a0ce4eb60cd"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac3ad3cf9e1d0f08e8d22a65115368f2b22b9e96403fa644e146e1221c65c454"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c4d3dca4acdbc5543bb00aee1e0715db797aa2819db5b7ca3feed3ab3366ff"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf7026851ea60d63c1a88f62439da78b68bfbfec192c781255e3cfb34b6efc12"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ae4a83678c41c2cdbf3c2b18fc46be32225260c7b4807087bdb43793ee90fa"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:678c67d60b35eced29777fe9398b6e6a6638156f143c80662a0c7c99ce115be7"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:9830af5aef2c17686d6e7c78c20b92c7b57c9d7921a03e4c549b48fe0e98c5c0"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:08651e17c868a6a22124b6ab71e939a5bb4737e0535f381ce35077dc8116c4b3"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:b58b4c944d7ce20cd7b426ae8834433b5b1152391960960b778b37803f0ffc1c"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2b38818468ddb0c8fc4b172031d65ced3be22ba82360c45909a0546b2857d3e4"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-win32.whl", hash = "sha256:91d1f2284d13b68f213d05b51cd6242f4cfa065d291af6f353f9cbedd28d8c0d"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:a5c724f95366ba7a2895147b0690609b6774384fa8621aa46a66cf332e4b612f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e37b33263fad4accdba45c8566566d45fc01f47fd4afa3e265df9e0e3581d4f4"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f1e29495bc968e37352c315d23a38462d7e77fcfa1597d005d17ed93f9f3103"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad8a774f545eb5471587e0389fca4f855f36d58901c65547796d59fc13aee458"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75a5a0251e4ceca127b26d18f0965b7f3c820a2dd2c51c25015c819300fd5859"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e40535f0f57e8b605e1cbce1399c96bcd5ab99e60992d2c7669c689d0cbe5"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e04e1dd62d019cde936d18fcd21361f6c4695e0e73fd6dc509c4ccd9446d26d"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2df377e3d04f5c427c9f79ef95bdf0b982bde76c1dbd4441f83268f3f1993a53"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:5f3cb8db19e7462ccb2e34b56feaccb2aac675ad8f77e28f8222b3e7c47d1f92"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:b40860b3e6d6108473836a29d3600e1b335192637e16e9421b43b58147ced3c1"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:ca4fd9e8b669e81bb74479bde61ee475d7a6832d667e2ce80e6136ddd7a0fedd"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:771e74a22f48c40b6402d0ca1d569ced5a796e118d4472da388744b5aa0ebd3f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-win32.whl", hash = "sha256:4589bfca18ecf787598262327f7329fe1f4fc2655c04899d84451562e2099a57"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:f646ab958a601bad8925a876f5aa68bdf0ec3584630143ed1ad8e9df4e447044"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dfb8106a05af1cb1eadeea996171b52c80f18851972e49ffe91539e4fc064b0f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b8b9b77a898b721fc634858fc43552119d3d303485adc6f28f3e76f028d5ea04"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c65efab1a0ab14f75314039694ac35d3181a5c8cf43584bd537b36caf2a6ccf9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b450eee7e201c48aae58e2d45ef5d309a19cd49952cfb58d546fefbeef0a100"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8f0997202d7628c4312f0398122bdc5ada7fa79939d248652af40d9da689ef8"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dae69bef7628236d426e408fb14a40f0027bac1658a06efd29549b26ba369372"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef48e874bcc3ebf623672ec99f9aaa7b8a4f62fb270e33dad6db3739ea111086"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9006dc1a73e2b2a53421aa72decbcff08cb109f67a20f7d15a64ab140e0a1d2"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:96c07a345740fa71c0d8fc5fa7ea182ee24f62ebbf33d4d10c8c72d866dc332d"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:85a78af6f6e782e0df36f93c9e7c2dd568204f60a2ea55025c21d1837dea95ec"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:19eadc55bf69f9e9799a808cdcfc6657cf30511cb32849235d555adfa048a99f"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:377e8ad3bb3c17c43f860b570fd15e048246ade92babc9b310f2c417500aca57"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f8e07aec529d6fa31516201c524b0cfac108a9a6044a148f236291aae7991195"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-win32.whl", hash = "sha256:703ab55b77b1c1ebb80eb0b27574a8eadf109739d252de7f646dc41cb82f1a65"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-win_amd64.whl", hash = "sha256:b4f4b2e019c6d1ad33d5fc3163d31d0f731a073a5a52cdfae7b85408548ce342"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a84e3b098a29b8c298b01291cf8bc850a507ca45507d43674a84a8d33b7595b2"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9905b580cfdbd6945e44d81332483deace167d33e956ffae5c4b27eddeb676e7"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c403a7418631dc7185ef8053acc765101f4f64cc0bf50d1bc44ae7d40fc28e"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d63f89bf28de4ce82a7c324275ce733bf31eb29ec1121e48261af89b5b7f30b"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc6dc782f5be4883279079c79fa88578258a0fd24651f6d69b0f4be2716f7d7e"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743c177822c60e66c5c9579b4f384bd98e60fd4a2abe0eacdec0af4747d925bc"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5eeb87220e4d2abf6faad1ecb3b3ee88c4d9caad6cf2ce4c0a73a91c4c7ad9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9318b814363b4bc062e54852ea62f58b69e7da9e51211afd6c55e9170e1ae9a0"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e7a1f58a2614f2ad9fcb4822f6da56313cbb88309880512bf5d01bd3d9142b87"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ae9427ddde670f605c84f704c12d840628467cc0f0a8e9ce6489577eef6a0479"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:be75ae311433254af3c6fe6eb68bf80ac6ef547ec0cf4594f5b301a044682186"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:73e1438437bbe67d453e2908b90b17b357a992a9ac0011ad20af1ea7c2b4cd58"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:393c0a9af080c1c1a0801cca9448eff3633dafc1a7934fdce58a8d1c15d8bd2b"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win32.whl", hash = "sha256:13a79fc8e9b69bf6d70e7fa5a53bd42fab83dc3e6e93da7fa82871ec40874e43"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win_amd64.whl", hash = "sha256:b1648708e5bf599b4455bf330faa4777c3963669acb2f3fa25240123a01f8b2b"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win_arm64.whl", hash = "sha256:4c3dd2f54bdd518b90e28b07c31cdfe34c8bd182c5107a30a9c2ef9569cd6cf9"}, | ||||
|     {file = "sqlcipher3_wheels-0.5.5.post0.tar.gz", hash = "sha256:2c291ba05fa3e57c9b4d407d2751aa69266b5372468e7402daaa312b251aca7f"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tomli" | ||||
| version = "2.3.0" | ||||
| description = "A lil' TOML parser" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, | ||||
|     {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, | ||||
|     {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, | ||||
|     {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, | ||||
|     {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, | ||||
|     {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, | ||||
|     {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "typing-extensions" | ||||
| version = "4.15.0" | ||||
| description = "Backported and Experimental Type Hints for Python 3.9+" | ||||
| optional = false | ||||
| python-versions = ">=3.9" | ||||
| files = [ | ||||
|     {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, | ||||
|     {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, | ||||
| ] | ||||
| 
 | ||||
| [metadata] | ||||
| lock-version = "2.0" | ||||
| python-versions = ">=3.9,<3.14" | ||||
| content-hash = "f5a670c96c370ce7d70dd76c7e2ebf98f7443e307b446779ea0c748db1019dd4" | ||||
							
								
								
									
										25
									
								
								pyproject.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								pyproject.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| [tool.poetry] | ||||
| name = "bouquin" | ||||
| version = "0.1.0" | ||||
| description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." | ||||
| authors = ["Miguel Jacq <mig@mig5.net>"] | ||||
| readme = "README.md" | ||||
| license = "GPL-3.0-or-later" | ||||
| 
 | ||||
| [tool.poetry.dependencies] | ||||
| python = ">=3.9,<3.14" | ||||
| pyside6 = ">=6.8.1,<7.0.0" | ||||
| sqlcipher3-wheels = "^0.5.5.post0" | ||||
| 
 | ||||
| [tool.poetry.scripts] | ||||
| bouquin = "bouquin.__main__:main" | ||||
| 
 | ||||
| [tool.poetry.group.test.dependencies] | ||||
| pytest = "^8.4.2" | ||||
| pytest-qt = "^4.5.0" | ||||
| pytest-mock = "^3.15.1" | ||||
| pytest-cov = "^7.0.0" | ||||
| 
 | ||||
| [build-system] | ||||
| requires = ["poetry-core"] | ||||
| build-backend = "poetry.core.masonry.api" | ||||
							
								
								
									
										
											BIN
										
									
								
								screenshot.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshot.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 55 KiB | 
							
								
								
									
										56
									
								
								tests/conftest.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tests/conftest.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| import os | ||||
| import pytest | ||||
| 
 | ||||
| # Run Qt without a visible display (CI-safe) | ||||
| os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def fake_key_prompt_cls(): | ||||
|     """A KeyPrompt stand-in that immediately returns Accepted with a fixed key.""" | ||||
|     from PySide6.QtWidgets import QDialog | ||||
| 
 | ||||
|     class FakeKeyPrompt: | ||||
|         accepted_count = 0 | ||||
| 
 | ||||
|         def __init__(self, *a, **k): | ||||
|             self._key = "sekret" | ||||
| 
 | ||||
|         def exec(self): | ||||
|             FakeKeyPrompt.accepted_count += 1 | ||||
|             return QDialog.Accepted | ||||
| 
 | ||||
|         def key(self): | ||||
|             return self._key | ||||
| 
 | ||||
|     return FakeKeyPrompt | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def fake_db_cls(): | ||||
|     """In-memory DB fake that mimics the subset of DBManager used by the UI.""" | ||||
|     class FakeDB: | ||||
|         def __init__(self, cfg): | ||||
|             self.cfg = cfg | ||||
|             self.data = {} | ||||
|             self.connected_key = None | ||||
|             self.closed = False | ||||
| 
 | ||||
|         def connect(self): | ||||
|             # record the key that UI supplied | ||||
|             self.connected_key = self.cfg.key | ||||
|             return True | ||||
| 
 | ||||
|         def get_entry(self, date_iso: str) -> str: | ||||
|             return self.data.get(date_iso, "") | ||||
| 
 | ||||
|         def upsert_entry(self, date_iso: str, content: str) -> None: | ||||
|             self.data[date_iso] = content | ||||
| 
 | ||||
|         def dates_with_content(self) -> list[str]: | ||||
|             return [d for d, t in self.data.items() if t.strip()] | ||||
| 
 | ||||
|         def close(self) -> None: | ||||
|             self.closed = True | ||||
| 
 | ||||
|     return FakeDB | ||||
							
								
								
									
										78
									
								
								tests/test_ui.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								tests/test_ui.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| # tests/test_main_window.py | ||||
| import pytest | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def patched_main_window(monkeypatch, qtbot, fake_db_cls, fake_key_prompt_cls): | ||||
|     """Construct MainWindow with faked DB + KeyPrompt so tests are deterministic.""" | ||||
|     mw_mod = pytest.importorskip("bouquin.main_window") | ||||
|     # Swap DBManager with in-memory fake | ||||
|     monkeypatch.setattr(mw_mod, "DBManager", fake_db_cls, raising=True) | ||||
|     # Make the unlock dialog auto-accept with a known key | ||||
|     monkeypatch.setattr(mw_mod, "KeyPrompt", fake_key_prompt_cls, raising=True) | ||||
| 
 | ||||
|     MainWindow = mw_mod.MainWindow | ||||
|     win = MainWindow() | ||||
|     qtbot.addWidget(win) | ||||
|     win.show() | ||||
|     return win, mw_mod, fake_db_cls, fake_key_prompt_cls | ||||
| 
 | ||||
| 
 | ||||
| def test_always_prompts_for_key_and_uses_it(patched_main_window): | ||||
|     win, mw_mod, FakeDB, FakeKP = patched_main_window | ||||
|     # The fake DB instance is on win.db; it records the key provided by the UI flow | ||||
|     assert isinstance(win.db, FakeDB) | ||||
|     assert win.db.connected_key == "sekret" | ||||
|     assert FakeKP.accepted_count >= 1  # was prompted at startup | ||||
| 
 | ||||
| 
 | ||||
| def test_manual_save_current_day(patched_main_window, qtbot): | ||||
|     win, *_ = patched_main_window | ||||
| 
 | ||||
|     # Type into the editor and save | ||||
|     win.editor.setPlainText("Test note") | ||||
|     win._save_current(explicit=True)  # call directly to avoid waiting timers | ||||
| 
 | ||||
|     day = win._current_date_iso() | ||||
|     assert win.db.get_entry(day) == "Test note" | ||||
| 
 | ||||
| 
 | ||||
| def test_switch_day_saves_previous(patched_main_window, qtbot): | ||||
|     from PySide6.QtCore import QDate | ||||
| 
 | ||||
|     win, *_ = patched_main_window | ||||
| 
 | ||||
|     # Write on Day 1 | ||||
|     d1 = win.calendar.selectedDate() | ||||
|     d1_iso = f"{d1.year():04d}-{d1.month():02d}-{d1.day():02d}" | ||||
|     win.editor.setPlainText("Notes day 1") | ||||
| 
 | ||||
|     # Trigger a day change (this path calls _on_date_changed via signal) | ||||
|     d2 = d1.addDays(1) | ||||
|     win.calendar.setSelectedDate(d2) | ||||
|     # After changing, previous day should be saved; editor now shows day 2 content (empty) | ||||
|     assert win.db.get_entry(d1_iso) == "Notes day 1" | ||||
|     assert win.editor.toPlainText() == "" | ||||
| 
 | ||||
| 
 | ||||
| def test_calendar_marks_refresh(patched_main_window, qtbot): | ||||
|     from PySide6.QtCore import QDate | ||||
|     from PySide6.QtGui import QTextCharFormat, QFont | ||||
| 
 | ||||
|     win, *_ = patched_main_window | ||||
| 
 | ||||
|     # Put content on two dates and refresh marks | ||||
|     today = win.calendar.selectedDate() | ||||
|     win.db.upsert_entry(f"{today.year():04d}-{today.month():02d}-{today.day():02d}", "x") | ||||
|     another = today.addDays(2) | ||||
|     win.db.upsert_entry(f"{another.year():04d}-{another.month():02d}-{another.day():02d}", "y") | ||||
| 
 | ||||
|     win._refresh_calendar_marks() | ||||
| 
 | ||||
|     fmt_today = win.calendar.dateTextFormat(today) | ||||
|     fmt_other = win.calendar.dateTextFormat(another) | ||||
| 
 | ||||
|     # Both should be bold (DemiBold or Bold depending on platform); we just assert non-Normal | ||||
|     assert fmt_today.fontWeight() != QFont.Weight.Normal | ||||
|     assert fmt_other.fontWeight() != QFont.Weight.Normal | ||||
| 
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue