From c7b86df581a522eb3a0c7ff9f5f7bb4650fb3124 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 29 Apr 2015 18:45:52 +0200 Subject: [PATCH 001/586] Initial commit --- .gitignore | 57 +++++++++ LICENSE | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 399 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba74660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cdb845 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This 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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..352891d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# stacosys +Static sites need a Commenting System From 4e2fca29373d5d1c9d3cc4606f14327fb469423b Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 29 Apr 2015 18:45:52 +0200 Subject: [PATCH 002/586] Initial commit --- .gitignore | 57 +++++++++ LICENSE | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 399 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba74660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cdb845 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This 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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..352891d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# stacosys +Static sites need a Commenting System From 4b1be51bcc7f6d553ef8ceee67b2f690fc48c473 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 29 Apr 2015 20:20:07 +0200 Subject: [PATCH 003/586] Migration tools from Pecosys to Stacosys --- requirements.txt | 6 ++++ tools/config.json | 3 ++ tools/pecosys2stacosys.py | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 requirements.txt create mode 100644 tools/config.json create mode 100644 tools/pecosys2stacosys.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b11b0cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Flask==0.10.1 +itsdangerous==0.24 +Jinja2==2.7.3 +MarkupSafe==0.23 +SQLAlchemy==1.0.2 +Werkzeug==0.10.4 diff --git a/tools/config.json b/tools/config.json new file mode 100644 index 0000000..ffdef07 --- /dev/null +++ b/tools/config.json @@ -0,0 +1,3 @@ +{ + "comments": "/home/yannic/work/coding/blog/blogduyax/comments" +} diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py new file mode 100644 index 0000000..c8da9c7 --- /dev/null +++ b/tools/pecosys2stacosys.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +import os +import re +import json +import logging +from clize import clize, run + + +# configure logging +level = logging.DEBUG +logger = logging.getLogger(__name__) +logger.setLevel(level) +ch = logging.StreamHandler() +ch.setLevel(level) +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +logger.addHandler(ch) + +# regex +regex = re.compile(r"(\w+):\s*(.*)") + + +def convert_comment(config, filename): + logger.info('convert %s' % filename) + d = {} + with open(filename) as f: + for line in f: + match = regex.match(line) + if match: + d[match.group(1)] = match.group(2) + else: + break + logger.debug(d) + + +def convert(config): + comment_dir = config['comments'] + logger.info('Comment directory %s' % comment_dir) + for dirpath, dirs, files in os.walk(comment_dir): + for filename in files: + if filename.endswith(('.md',)): + comment_file = '/'.join([dirpath, filename]) + convert_comment(config, comment_file) + else: + logger.debug('ignore file %s' % filename) + + +def load_config(config_pathname): + logger.info("Load config from %s" % config_pathname) + with open(config_pathname, 'rt') as config_file: + config = json.loads(config_file.read()) + return config + + +@clize +def pecosys2stacosys(config_pathname): + config = load_config(config_pathname) + convert(config) + + +if __name__ == '__main__': + run(pecosys2stacosys) From 66fa62d6bec7da8d3377f086760caac7d876141b Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 29 Apr 2015 20:20:07 +0200 Subject: [PATCH 004/586] Migration tools from Pecosys to Stacosys --- requirements.txt | 6 ++++ tools/config.json | 3 ++ tools/pecosys2stacosys.py | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 requirements.txt create mode 100644 tools/config.json create mode 100644 tools/pecosys2stacosys.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b11b0cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Flask==0.10.1 +itsdangerous==0.24 +Jinja2==2.7.3 +MarkupSafe==0.23 +SQLAlchemy==1.0.2 +Werkzeug==0.10.4 diff --git a/tools/config.json b/tools/config.json new file mode 100644 index 0000000..ffdef07 --- /dev/null +++ b/tools/config.json @@ -0,0 +1,3 @@ +{ + "comments": "/home/yannic/work/coding/blog/blogduyax/comments" +} diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py new file mode 100644 index 0000000..c8da9c7 --- /dev/null +++ b/tools/pecosys2stacosys.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +import os +import re +import json +import logging +from clize import clize, run + + +# configure logging +level = logging.DEBUG +logger = logging.getLogger(__name__) +logger.setLevel(level) +ch = logging.StreamHandler() +ch.setLevel(level) +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +logger.addHandler(ch) + +# regex +regex = re.compile(r"(\w+):\s*(.*)") + + +def convert_comment(config, filename): + logger.info('convert %s' % filename) + d = {} + with open(filename) as f: + for line in f: + match = regex.match(line) + if match: + d[match.group(1)] = match.group(2) + else: + break + logger.debug(d) + + +def convert(config): + comment_dir = config['comments'] + logger.info('Comment directory %s' % comment_dir) + for dirpath, dirs, files in os.walk(comment_dir): + for filename in files: + if filename.endswith(('.md',)): + comment_file = '/'.join([dirpath, filename]) + convert_comment(config, comment_file) + else: + logger.debug('ignore file %s' % filename) + + +def load_config(config_pathname): + logger.info("Load config from %s" % config_pathname) + with open(config_pathname, 'rt') as config_file: + config = json.loads(config_file.read()) + return config + + +@clize +def pecosys2stacosys(config_pathname): + config = load_config(config_pathname) + convert(config) + + +if __name__ == '__main__': + run(pecosys2stacosys) From b38d47228a0bf0121cda1f11b07188e0307dad5f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 29 Apr 2015 20:26:00 +0200 Subject: [PATCH 005/586] Change .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ba74660..faac339 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # PyBuilder target/ + +# My ignored files and directories +myconfig.json From 451bf1915ee3c51e64f36e5290018cad6b25ad96 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 29 Apr 2015 20:26:00 +0200 Subject: [PATCH 006/586] Change .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ba74660..faac339 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # PyBuilder target/ + +# My ignored files and directories +myconfig.json From 7b5c0822ff97843dc5d59e62250c192b71c45734 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 1 May 2015 18:57:17 +0200 Subject: [PATCH 007/586] Conversion tool is near from complete. Use Peewee as ORM --- app/models/comment.py | 24 +++++++++++++ app/models/site.py | 15 +++++++++ app/services/database.py | 39 +++++++++++++++++++++ config.py | 3 ++ requirements.txt | 4 ++- tools/config.json | 3 -- tools/pecosys2stacosys.py | 71 ++++++++++++++++++++++++++++++--------- 7 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 app/models/comment.py create mode 100644 app/models/site.py create mode 100644 app/services/database.py create mode 100644 config.py delete mode 100644 tools/config.json diff --git a/app/models/comment.py b/app/models/comment.py new file mode 100644 index 0000000..f86d4e1 --- /dev/null +++ b/app/models/comment.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import DateTimeField +from peewee import IntegerField +from peewee import ForeignKeyField +from app.models.site import Site +from app.services.database import get_db + + +class Comment(Model): + url = CharField() + date = DateTimeField() + rel_index = IntegerField() + author_name = CharField() + author_email = CharField(default='') + author_site = CharField() + content = CharField() + site = ForeignKeyField(Site, related_name='site') + + class Meta: + database = get_db() diff --git a/app/models/site.py b/app/models/site.py new file mode 100644 index 0000000..413dd82 --- /dev/null +++ b/app/models/site.py @@ -0,0 +1,15 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from app.services.database import get_db + + +class Site(Model): + name = CharField(unique=True) + url = CharField() + token = CharField() + + class Meta: + database = get_db() diff --git a/app/services/database.py b/app/services/database.py new file mode 100644 index 0000000..1d6028e --- /dev/null +++ b/app/services/database.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import hashlib +import config +import functools +from config import DB_URL +from playhouse.db_url import connect + + +def get_db(): + return connect(DB_URL) + + +def provide_db(func): + + @functools.wraps(func) + def new_function(*args, **kwargs): + return func(get_db(), *args, **kwargs) + + return new_function + + +def hash(value): + string = '%s%s' % (value, config.SALT) + dk = hashlib.sha256(string.encode()) + return dk.hexdigest() + + +@provide_db +def setup(db): + from app.models.user import User + db.create_tables([User], safe=True) + + # create admin user if user table is empty + if User.select().count() == 0: + admin_user = User(username='admin', password=hash('admin'), + displayname='Admin') + admin_user.save() diff --git a/config.py b/config.py new file mode 100644 index 0000000..c0fe729 --- /dev/null +++ b/config.py @@ -0,0 +1,3 @@ +# Configuration file + +DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" diff --git a/requirements.txt b/requirements.txt index b11b0cb..6f03f6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ +clize==2.4 Flask==0.10.1 itsdangerous==0.24 Jinja2==2.7.3 MarkupSafe==0.23 -SQLAlchemy==1.0.2 +peewee==2.6.0 +PyMySQL==0.6.6 Werkzeug==0.10.4 diff --git a/tools/config.json b/tools/config.json deleted file mode 100644 index ffdef07..0000000 --- a/tools/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "comments": "/home/yannic/work/coding/blog/blogduyax/comments" -} diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index c8da9c7..dcca732 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -1,12 +1,24 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- +import sys import os import re -import json import logging from clize import clize, run +# add necessary directories to PATH +current_path = os.path.realpath('.') +parent_path = os.path.abspath(os.path.join(current_path, '..')) +paths = [current_path, parent_path] +for path in paths: + if path not in sys.path: + sys.path.insert(0, path) + +# import database models +from app.services.database import provide_db +from app.models.site import Site +from app.models.comment import Comment # configure logging level = logging.DEBUG @@ -22,9 +34,10 @@ logger.addHandler(ch) regex = re.compile(r"(\w+):\s*(.*)") -def convert_comment(config, filename): +def convert_comment(db, site, filename): logger.info('convert %s' % filename) d = {} + content = '' with open(filename) as f: for line in f: match = regex.match(line) @@ -32,32 +45,60 @@ def convert_comment(config, filename): d[match.group(1)] = match.group(2) else: break + is_header = True + for line in f: + if is_header: + if line.strip(): + is_header = False + else: + continue + content = content + line logger.debug(d) + logger.debug(content) + + # create DB record + comment = Comment(site=site, author_name=d['author'], content=content) + if 'email' in d: + comment.author_email = d['email'] + if 'site' in d: + comment.author_site = d['site'] + if 'url' in d: + comment.url = d['url'] + # else: + # comment.url = d['article'] + if 'date' in d: + comment.date = d['date'] + comment.save() -def convert(config): - comment_dir = config['comments'] +@provide_db +def convert(db, site_name, url, comment_dir): + + # create DB tables if needed + db.create_tables([Site, Comment], safe=True) + + # delete site record + try: + site = Site.select().where(Site.name == site_name).get() + site.delete_instance(recursive=True) + except Site.DoesNotExist: + pass + + site = Site.create(name=site_name, url=url, token='') + logger.info('Comment directory %s' % comment_dir) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) - convert_comment(config, comment_file) + convert_comment(db, site, comment_file) else: logger.debug('ignore file %s' % filename) -def load_config(config_pathname): - logger.info("Load config from %s" % config_pathname) - with open(config_pathname, 'rt') as config_file: - config = json.loads(config_file.read()) - return config - - @clize -def pecosys2stacosys(config_pathname): - config = load_config(config_pathname) - convert(config) +def pecosys2stacosys(site, url, comment_dir): + convert(site, url, comment_dir) if __name__ == '__main__': From 7441a8021706ac7514728fec17e0ea385e8278dc Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 1 May 2015 18:57:17 +0200 Subject: [PATCH 008/586] Conversion tool is near from complete. Use Peewee as ORM --- app/models/comment.py | 24 +++++++++++++ app/models/site.py | 15 +++++++++ app/services/database.py | 39 +++++++++++++++++++++ config.py | 3 ++ requirements.txt | 4 ++- tools/config.json | 3 -- tools/pecosys2stacosys.py | 71 ++++++++++++++++++++++++++++++--------- 7 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 app/models/comment.py create mode 100644 app/models/site.py create mode 100644 app/services/database.py create mode 100644 config.py delete mode 100644 tools/config.json diff --git a/app/models/comment.py b/app/models/comment.py new file mode 100644 index 0000000..f86d4e1 --- /dev/null +++ b/app/models/comment.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import DateTimeField +from peewee import IntegerField +from peewee import ForeignKeyField +from app.models.site import Site +from app.services.database import get_db + + +class Comment(Model): + url = CharField() + date = DateTimeField() + rel_index = IntegerField() + author_name = CharField() + author_email = CharField(default='') + author_site = CharField() + content = CharField() + site = ForeignKeyField(Site, related_name='site') + + class Meta: + database = get_db() diff --git a/app/models/site.py b/app/models/site.py new file mode 100644 index 0000000..413dd82 --- /dev/null +++ b/app/models/site.py @@ -0,0 +1,15 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from app.services.database import get_db + + +class Site(Model): + name = CharField(unique=True) + url = CharField() + token = CharField() + + class Meta: + database = get_db() diff --git a/app/services/database.py b/app/services/database.py new file mode 100644 index 0000000..1d6028e --- /dev/null +++ b/app/services/database.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import hashlib +import config +import functools +from config import DB_URL +from playhouse.db_url import connect + + +def get_db(): + return connect(DB_URL) + + +def provide_db(func): + + @functools.wraps(func) + def new_function(*args, **kwargs): + return func(get_db(), *args, **kwargs) + + return new_function + + +def hash(value): + string = '%s%s' % (value, config.SALT) + dk = hashlib.sha256(string.encode()) + return dk.hexdigest() + + +@provide_db +def setup(db): + from app.models.user import User + db.create_tables([User], safe=True) + + # create admin user if user table is empty + if User.select().count() == 0: + admin_user = User(username='admin', password=hash('admin'), + displayname='Admin') + admin_user.save() diff --git a/config.py b/config.py new file mode 100644 index 0000000..c0fe729 --- /dev/null +++ b/config.py @@ -0,0 +1,3 @@ +# Configuration file + +DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" diff --git a/requirements.txt b/requirements.txt index b11b0cb..6f03f6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ +clize==2.4 Flask==0.10.1 itsdangerous==0.24 Jinja2==2.7.3 MarkupSafe==0.23 -SQLAlchemy==1.0.2 +peewee==2.6.0 +PyMySQL==0.6.6 Werkzeug==0.10.4 diff --git a/tools/config.json b/tools/config.json deleted file mode 100644 index ffdef07..0000000 --- a/tools/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "comments": "/home/yannic/work/coding/blog/blogduyax/comments" -} diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index c8da9c7..dcca732 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -1,12 +1,24 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- +import sys import os import re -import json import logging from clize import clize, run +# add necessary directories to PATH +current_path = os.path.realpath('.') +parent_path = os.path.abspath(os.path.join(current_path, '..')) +paths = [current_path, parent_path] +for path in paths: + if path not in sys.path: + sys.path.insert(0, path) + +# import database models +from app.services.database import provide_db +from app.models.site import Site +from app.models.comment import Comment # configure logging level = logging.DEBUG @@ -22,9 +34,10 @@ logger.addHandler(ch) regex = re.compile(r"(\w+):\s*(.*)") -def convert_comment(config, filename): +def convert_comment(db, site, filename): logger.info('convert %s' % filename) d = {} + content = '' with open(filename) as f: for line in f: match = regex.match(line) @@ -32,32 +45,60 @@ def convert_comment(config, filename): d[match.group(1)] = match.group(2) else: break + is_header = True + for line in f: + if is_header: + if line.strip(): + is_header = False + else: + continue + content = content + line logger.debug(d) + logger.debug(content) + + # create DB record + comment = Comment(site=site, author_name=d['author'], content=content) + if 'email' in d: + comment.author_email = d['email'] + if 'site' in d: + comment.author_site = d['site'] + if 'url' in d: + comment.url = d['url'] + # else: + # comment.url = d['article'] + if 'date' in d: + comment.date = d['date'] + comment.save() -def convert(config): - comment_dir = config['comments'] +@provide_db +def convert(db, site_name, url, comment_dir): + + # create DB tables if needed + db.create_tables([Site, Comment], safe=True) + + # delete site record + try: + site = Site.select().where(Site.name == site_name).get() + site.delete_instance(recursive=True) + except Site.DoesNotExist: + pass + + site = Site.create(name=site_name, url=url, token='') + logger.info('Comment directory %s' % comment_dir) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) - convert_comment(config, comment_file) + convert_comment(db, site, comment_file) else: logger.debug('ignore file %s' % filename) -def load_config(config_pathname): - logger.info("Load config from %s" % config_pathname) - with open(config_pathname, 'rt') as config_file: - config = json.loads(config_file.read()) - return config - - @clize -def pecosys2stacosys(config_pathname): - config = load_config(config_pathname) - convert(config) +def pecosys2stacosys(site, url, comment_dir): + convert(site, url, comment_dir) if __name__ == '__main__': From 2053e116a43579b3a63b74f41c8b6b934479abb9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 1 May 2015 19:29:26 +0200 Subject: [PATCH 009/586] relative index is not useful. Peewee has paging capability built-in --- app/models/comment.py | 9 ++++----- tools/pecosys2stacosys.py | 7 ++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/models/comment.py b/app/models/comment.py index f86d4e1..910597a 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -3,8 +3,8 @@ from peewee import Model from peewee import CharField +from peewee import TextField from peewee import DateTimeField -from peewee import IntegerField from peewee import ForeignKeyField from app.models.site import Site from app.services.database import get_db @@ -12,12 +12,11 @@ from app.services.database import get_db class Comment(Model): url = CharField() - date = DateTimeField() - rel_index = IntegerField() + published = DateTimeField() author_name = CharField() author_email = CharField(default='') - author_site = CharField() - content = CharField() + author_site = CharField(default='') + content = TextField() site = ForeignKeyField(Site, related_name='site') class Meta: diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index dcca732..c06e098 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -53,8 +53,6 @@ def convert_comment(db, site, filename): else: continue content = content + line - logger.debug(d) - logger.debug(content) # create DB record comment = Comment(site=site, author_name=d['author'], content=content) @@ -67,7 +65,7 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: - comment.date = d['date'] + comment.published = d['date'] comment.save() @@ -86,14 +84,13 @@ def convert(db, site_name, url, comment_dir): site = Site.create(name=site_name, url=url, token='') - logger.info('Comment directory %s' % comment_dir) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) convert_comment(db, site, comment_file) else: - logger.debug('ignore file %s' % filename) + logger.warn('ignore file %s' % filename) @clize From a116d5bc01db915aa19f72c9b13c7ee1aa6d1b45 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 1 May 2015 19:29:26 +0200 Subject: [PATCH 010/586] relative index is not useful. Peewee has paging capability built-in --- app/models/comment.py | 9 ++++----- tools/pecosys2stacosys.py | 7 ++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/models/comment.py b/app/models/comment.py index f86d4e1..910597a 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -3,8 +3,8 @@ from peewee import Model from peewee import CharField +from peewee import TextField from peewee import DateTimeField -from peewee import IntegerField from peewee import ForeignKeyField from app.models.site import Site from app.services.database import get_db @@ -12,12 +12,11 @@ from app.services.database import get_db class Comment(Model): url = CharField() - date = DateTimeField() - rel_index = IntegerField() + published = DateTimeField() author_name = CharField() author_email = CharField(default='') - author_site = CharField() - content = CharField() + author_site = CharField(default='') + content = TextField() site = ForeignKeyField(Site, related_name='site') class Meta: diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index dcca732..c06e098 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -53,8 +53,6 @@ def convert_comment(db, site, filename): else: continue content = content + line - logger.debug(d) - logger.debug(content) # create DB record comment = Comment(site=site, author_name=d['author'], content=content) @@ -67,7 +65,7 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: - comment.date = d['date'] + comment.published = d['date'] comment.save() @@ -86,14 +84,13 @@ def convert(db, site_name, url, comment_dir): site = Site.create(name=site_name, url=url, token='') - logger.info('Comment directory %s' % comment_dir) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) convert_comment(db, site, comment_file) else: - logger.debug('ignore file %s' % filename) + logger.warn('ignore file %s' % filename) @clize From 203438ab7cf1c9ea07f597a6dd486ee1d57fe041 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 13:08:07 +0200 Subject: [PATCH 011/586] Bootstrap Flask application --- app/server.py | 52 ++++++++++++++++++++++++++++++++++++++++ app/services/database.py | 10 +++----- config.py | 5 ++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 app/server.py diff --git a/app/server.py b/app/server.py new file mode 100644 index 0000000..59aa784 --- /dev/null +++ b/app/server.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import os +import sys +import logging +from flask import Flask +from werkzeug.contrib.fixers import ProxyFix + +app = Flask(__name__) + +# add current and parent path to syspath +currentPath = os.path.dirname(__file__) +parentPath = os.path.abspath(os.path.join(currentPath, os.path.pardir)) +paths = [currentPath, parentPath] +for path in paths: + if path not in sys.path: + sys.path.insert(0, path) + +# configure logging + +import config + + +def configure_logging(level): + root_logger = logging.getLogger() + root_logger.setLevel(level) + ch = logging.StreamHandler() + ch.setLevel(level) + # create formatter + formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + # add formatter to ch + ch.setFormatter(formatter) + # add ch to logger + root_logger.addHandler(ch) + +logging_level = (20, 10)[config.DEBUG] +configure_logging(logging_level) + +logger = logging.getLogger(__name__) + +# initialize database +from app.services import database +database.setup() + +app.wsgi_app = ProxyFix(app.wsgi_app) + +logger.info("Start Stacosys application") + +app.run(host=config.HTTP_ADDRESS, + port=config.HTTP_PORT, + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/database.py b/app/services/database.py index 1d6028e..e3baf82 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -29,11 +29,7 @@ def hash(value): @provide_db def setup(db): - from app.models.user import User - db.create_tables([User], safe=True) + from app.models.site import Site + from app.models.comment import Comment - # create admin user if user table is empty - if User.select().count() == 0: - admin_user = User(username='admin', password=hash('admin'), - displayname='Admin') - admin_user.save() + db.create_tables([Site, Comment], safe=True) diff --git a/config.py b/config.py index c0fe729..351dc1f 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,8 @@ # Configuration file +DEBUG = True + DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" + +HTTP_ADDRESS = "0.0.0.0" +HTTP_PORT = 8000 From e3fd4f20c98e8524daebcfe3c436e49ffe237bf4 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 13:08:07 +0200 Subject: [PATCH 012/586] Bootstrap Flask application --- app/server.py | 52 ++++++++++++++++++++++++++++++++++++++++ app/services/database.py | 10 +++----- config.py | 5 ++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 app/server.py diff --git a/app/server.py b/app/server.py new file mode 100644 index 0000000..59aa784 --- /dev/null +++ b/app/server.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import os +import sys +import logging +from flask import Flask +from werkzeug.contrib.fixers import ProxyFix + +app = Flask(__name__) + +# add current and parent path to syspath +currentPath = os.path.dirname(__file__) +parentPath = os.path.abspath(os.path.join(currentPath, os.path.pardir)) +paths = [currentPath, parentPath] +for path in paths: + if path not in sys.path: + sys.path.insert(0, path) + +# configure logging + +import config + + +def configure_logging(level): + root_logger = logging.getLogger() + root_logger.setLevel(level) + ch = logging.StreamHandler() + ch.setLevel(level) + # create formatter + formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + # add formatter to ch + ch.setFormatter(formatter) + # add ch to logger + root_logger.addHandler(ch) + +logging_level = (20, 10)[config.DEBUG] +configure_logging(logging_level) + +logger = logging.getLogger(__name__) + +# initialize database +from app.services import database +database.setup() + +app.wsgi_app = ProxyFix(app.wsgi_app) + +logger.info("Start Stacosys application") + +app.run(host=config.HTTP_ADDRESS, + port=config.HTTP_PORT, + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/database.py b/app/services/database.py index 1d6028e..e3baf82 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -29,11 +29,7 @@ def hash(value): @provide_db def setup(db): - from app.models.user import User - db.create_tables([User], safe=True) + from app.models.site import Site + from app.models.comment import Comment - # create admin user if user table is empty - if User.select().count() == 0: - admin_user = User(username='admin', password=hash('admin'), - displayname='Admin') - admin_user.save() + db.create_tables([Site, Comment], safe=True) diff --git a/config.py b/config.py index c0fe729..351dc1f 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,8 @@ # Configuration file +DEBUG = True + DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" + +HTTP_ADDRESS = "0.0.0.0" +HTTP_PORT = 8000 From 3338ba51d803b1601c603e57d9f64e702a44559a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 13:11:18 +0200 Subject: [PATCH 013/586] Add run.sh --- run.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 run.sh diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0f471c8 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh +python app/server.py "$@" + From 89cb8c09430eed9174ff807ff9bba13bd1e4029b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 13:11:18 +0200 Subject: [PATCH 014/586] Add run.sh --- run.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 run.sh diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0f471c8 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh +python app/server.py "$@" + From 39039fd533b9457e2891c6b3f9b4af3628ea199e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 13:43:38 +0200 Subject: [PATCH 015/586] Add first controller --- app/__init__.py | 3 +++ app/controllers/api.py | 12 ++++++++++++ app/{server.py => run.py} | 12 +++++------- run.sh | 2 +- 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/controllers/api.py rename app/{server.py => run.py} (94%) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..d7562aa --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask + +app = Flask(__name__) diff --git a/app/controllers/api.py b/app/controllers/api.py new file mode 100644 index 0000000..0189c65 --- /dev/null +++ b/app/controllers/api.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from app import app + +logger = logging.getLogger(__name__) + + +@app.route("/comments", methods=['GET']) +def get_comments(): + return "OK" diff --git a/app/server.py b/app/run.py similarity index 94% rename from app/server.py rename to app/run.py index 59aa784..8c288b1 100644 --- a/app/server.py +++ b/app/run.py @@ -4,11 +4,8 @@ import os import sys import logging -from flask import Flask from werkzeug.contrib.fixers import ProxyFix -app = Flask(__name__) - # add current and parent path to syspath currentPath = os.path.dirname(__file__) parentPath = os.path.abspath(os.path.join(currentPath, os.path.pardir)) @@ -17,11 +14,13 @@ for path in paths: if path not in sys.path: sys.path.insert(0, path) -# configure logging - +# more imports import config +from app.services import database +from app.controllers import api +from app import app - +# configure logging def configure_logging(level): root_logger = logging.getLogger() root_logger.setLevel(level) @@ -40,7 +39,6 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) # initialize database -from app.services import database database.setup() app.wsgi_app = ProxyFix(app.wsgi_app) diff --git a/run.sh b/run.sh index 0f471c8..a3b0364 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/server.py "$@" +python app/run.py "$@" From 9c49c842f6ea65045b5320e6e147719aa560717b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 13:43:38 +0200 Subject: [PATCH 016/586] Add first controller --- app/__init__.py | 3 +++ app/controllers/api.py | 12 ++++++++++++ app/{server.py => run.py} | 12 +++++------- run.sh | 2 +- 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/controllers/api.py rename app/{server.py => run.py} (94%) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..d7562aa --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask + +app = Flask(__name__) diff --git a/app/controllers/api.py b/app/controllers/api.py new file mode 100644 index 0000000..0189c65 --- /dev/null +++ b/app/controllers/api.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from app import app + +logger = logging.getLogger(__name__) + + +@app.route("/comments", methods=['GET']) +def get_comments(): + return "OK" diff --git a/app/server.py b/app/run.py similarity index 94% rename from app/server.py rename to app/run.py index 59aa784..8c288b1 100644 --- a/app/server.py +++ b/app/run.py @@ -4,11 +4,8 @@ import os import sys import logging -from flask import Flask from werkzeug.contrib.fixers import ProxyFix -app = Flask(__name__) - # add current and parent path to syspath currentPath = os.path.dirname(__file__) parentPath = os.path.abspath(os.path.join(currentPath, os.path.pardir)) @@ -17,11 +14,13 @@ for path in paths: if path not in sys.path: sys.path.insert(0, path) -# configure logging - +# more imports import config +from app.services import database +from app.controllers import api +from app import app - +# configure logging def configure_logging(level): root_logger = logging.getLogger() root_logger.setLevel(level) @@ -40,7 +39,6 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) # initialize database -from app.services import database database.setup() app.wsgi_app = ProxyFix(app.wsgi_app) diff --git a/run.sh b/run.sh index 0f471c8..a3b0364 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/server.py "$@" +python app/run.py "$@" From 1521daf4856fb3aa5ed6b0f4b50adb9b78e0a634 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 20:24:12 +0200 Subject: [PATCH 017/586] Remove protocol part from URL --- tools/pecosys2stacosys.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index c06e098..69f5c27 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -61,7 +61,11 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'] if 'url' in d: - comment.url = d['url'] + logger.info(d['url'][:6]) + if d['url'][:7] == 'http://': + comment.url = d['url'][7:] + elif d['url'][:8] == 'https://': + comment.url = d['url'][8:] # else: # comment.url = d['article'] if 'date' in d: From a0bb9938fa2fbd90b00860d80a8424ea5208dab4 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 20:24:12 +0200 Subject: [PATCH 018/586] Remove protocol part from URL --- tools/pecosys2stacosys.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index c06e098..69f5c27 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -61,7 +61,11 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'] if 'url' in d: - comment.url = d['url'] + logger.info(d['url'][:6]) + if d['url'][:7] == 'http://': + comment.url = d['url'][7:] + elif d['url'][:8] == 'https://': + comment.url = d['url'][8:] # else: # comment.url = d['article'] if 'date' in d: From f021a9ff49c41d67b06dceb60713ed271c9b1260 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 20:24:56 +0200 Subject: [PATCH 019/586] Progress on API to retrieve comments --- app/controllers/api.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0189c65..52a01d7 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,11 +2,29 @@ # -*- coding: utf-8 -*- import logging +from flask import request, jsonify +from playhouse.shortcuts import model_to_dict from app import app +from app.models.site import Site +from app.models.comment import Comment logger = logging.getLogger(__name__) -@app.route("/comments", methods=['GET']) -def get_comments(): - return "OK" +@app.route("/comments", methods=['POST']) +def query_comments(): + + query = request.json + token = query['token'] + url = query['url'] + logger.info('token=%s url=%s' % (token, url)) + + comments = [] + for comment in Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).order_by(Comment.published): + comments.append(model_to_dict(comment)) + + r = jsonify({'data': comments}) + r.status_code = 200 + return r From 6681a721a29c6091e640983b29e7e36fb281b07c Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 20:24:56 +0200 Subject: [PATCH 020/586] Progress on API to retrieve comments --- app/controllers/api.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0189c65..52a01d7 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,11 +2,29 @@ # -*- coding: utf-8 -*- import logging +from flask import request, jsonify +from playhouse.shortcuts import model_to_dict from app import app +from app.models.site import Site +from app.models.comment import Comment logger = logging.getLogger(__name__) -@app.route("/comments", methods=['GET']) -def get_comments(): - return "OK" +@app.route("/comments", methods=['POST']) +def query_comments(): + + query = request.json + token = query['token'] + url = query['url'] + logger.info('token=%s url=%s' % (token, url)) + + comments = [] + for comment in Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).order_by(Comment.published): + comments.append(model_to_dict(comment)) + + r = jsonify({'data': comments}) + r.status_code = 200 + return r From 4c4ee442d3e1bdd3584246e71c50cf039503e498 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 May 2015 20:29:08 +0200 Subject: [PATCH 021/586] Add created datetime to Comment model --- app/models/comment.py | 1 + tools/pecosys2stacosys.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/comment.py b/app/models/comment.py index 910597a..ab80933 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -12,6 +12,7 @@ from app.services.database import get_db class Comment(Model): url = CharField() + created = DateTimeField() published = DateTimeField() author_name = CharField() author_email = CharField(default='') diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 69f5c27..8610f98 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -61,7 +61,6 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'] if 'url' in d: - logger.info(d['url'][:6]) if d['url'][:7] == 'http://': comment.url = d['url'][7:] elif d['url'][:8] == 'https://': @@ -69,6 +68,7 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: + comment.created = d['date'] comment.published = d['date'] comment.save() From f9accbffefeb8360b995b6a666fff909fd852460 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 May 2015 20:29:08 +0200 Subject: [PATCH 022/586] Add created datetime to Comment model --- app/models/comment.py | 1 + tools/pecosys2stacosys.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/comment.py b/app/models/comment.py index 910597a..ab80933 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -12,6 +12,7 @@ from app.services.database import get_db class Comment(Model): url = CharField() + created = DateTimeField() published = DateTimeField() author_name = CharField() author_email = CharField(default='') diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 69f5c27..8610f98 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -61,7 +61,6 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'] if 'url' in d: - logger.info(d['url'][:6]) if d['url'][:7] == 'http://': comment.url = d['url'][7:] elif d['url'][:8] == 'https://': @@ -69,6 +68,7 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: + comment.created = d['date'] comment.published = d['date'] comment.save() From 612fb85fa34035f713ce1471a0ceafccffc7ea85 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 May 2015 18:31:15 +0200 Subject: [PATCH 023/586] Finalize migration tool. Retrieve comments by GET and POST --- app/controllers/api.py | 41 ++++++++++++++++++++++++++------------- app/helpers/hashing.py | 16 +++++++++++++++ app/services/database.py | 10 +--------- config.py | 2 ++ tools/pecosys2stacosys.py | 11 ++++++----- 5 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 app/helpers/hashing.py diff --git a/app/controllers/api.py b/app/controllers/api.py index 52a01d7..287712f 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -3,28 +3,43 @@ import logging from flask import request, jsonify -from playhouse.shortcuts import model_to_dict from app import app from app.models.site import Site from app.models.comment import Comment +from app.helpers.hashing import md5 logger = logging.getLogger(__name__) -@app.route("/comments", methods=['POST']) +@app.route("/comments", methods=['GET', 'POST']) def query_comments(): - query = request.json - token = query['token'] - url = query['url'] - logger.info('token=%s url=%s' % (token, url)) - comments = [] - for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Site.token == token)).order_by(Comment.published): - comments.append(model_to_dict(comment)) + try: + if request.method == 'POST': + token = request.json['token'] + url = request.json['url'] + else: + token = request.args.get('token', '') + url = request.args.get('url', '') - r = jsonify({'data': comments}) - r.status_code = 200 + logger.info('retrieve comments for token %s, url %s' % (token, url)) + for comment in Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).order_by(Comment.published): + d = {} + d['author'] = comment.author_name + d['content'] = comment.content + if comment.author_site: + d['site'] = comment.author_site + if comment.author_email: + d['avatar'] = md5(comment.author_email.strip().lower()) + d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + comments.append(d) + r = jsonify({'data': comments}) + r.status_code = 200 + except: + logger.warn('bad request') + r = jsonify({'data': []}) + r.status_code = 400 return r diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py new file mode 100644 index 0000000..2d91635 --- /dev/null +++ b/app/helpers/hashing.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import hashlib +import config + + +def salt(value): + string = '%s%s' % (value, config.SALT) + dk = hashlib.sha256(string.encode()) + return dk.hexdigest() + + +def md5(value): + dk = hashlib.md5(value.encode()) + return dk.hexdigest() diff --git a/app/services/database.py b/app/services/database.py index e3baf82..f0fc403 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -1,15 +1,13 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import hashlib import config import functools -from config import DB_URL from playhouse.db_url import connect def get_db(): - return connect(DB_URL) + return connect(config.DB_URL) def provide_db(func): @@ -21,12 +19,6 @@ def provide_db(func): return new_function -def hash(value): - string = '%s%s' % (value, config.SALT) - dk = hashlib.sha256(string.encode()) - return dk.hexdigest() - - @provide_db def setup(db): from app.models.site import Site diff --git a/config.py b/config.py index 351dc1f..00c8647 100644 --- a/config.py +++ b/config.py @@ -6,3 +6,5 @@ DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 + +SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 8610f98..600ab6d 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -17,6 +17,7 @@ for path in paths: # import database models from app.services.database import provide_db +from app.helpers.hashing import salt from app.models.site import Site from app.models.comment import Comment @@ -57,14 +58,14 @@ def convert_comment(db, site, filename): # create DB record comment = Comment(site=site, author_name=d['author'], content=content) if 'email' in d: - comment.author_email = d['email'] + comment.author_email = d['email'].strip() if 'site' in d: - comment.author_site = d['site'] + comment.author_site = d['site'].strip() if 'url' in d: if d['url'][:7] == 'http://': - comment.url = d['url'][7:] + comment.url = d['url'][7:].strip() elif d['url'][:8] == 'https://': - comment.url = d['url'][8:] + comment.url = d['url'][8:].strip() # else: # comment.url = d['article'] if 'date' in d: @@ -86,7 +87,7 @@ def convert(db, site_name, url, comment_dir): except Site.DoesNotExist: pass - site = Site.create(name=site_name, url=url, token='') + site = Site.create(name=site_name, url=url, token=salt(url)) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: From 2d29502e5f990ae937662e0fbe33020346470a48 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 May 2015 18:31:15 +0200 Subject: [PATCH 024/586] Finalize migration tool. Retrieve comments by GET and POST --- app/controllers/api.py | 41 ++++++++++++++++++++++++++------------- app/helpers/hashing.py | 16 +++++++++++++++ app/services/database.py | 10 +--------- config.py | 2 ++ tools/pecosys2stacosys.py | 11 ++++++----- 5 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 app/helpers/hashing.py diff --git a/app/controllers/api.py b/app/controllers/api.py index 52a01d7..287712f 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -3,28 +3,43 @@ import logging from flask import request, jsonify -from playhouse.shortcuts import model_to_dict from app import app from app.models.site import Site from app.models.comment import Comment +from app.helpers.hashing import md5 logger = logging.getLogger(__name__) -@app.route("/comments", methods=['POST']) +@app.route("/comments", methods=['GET', 'POST']) def query_comments(): - query = request.json - token = query['token'] - url = query['url'] - logger.info('token=%s url=%s' % (token, url)) - comments = [] - for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Site.token == token)).order_by(Comment.published): - comments.append(model_to_dict(comment)) + try: + if request.method == 'POST': + token = request.json['token'] + url = request.json['url'] + else: + token = request.args.get('token', '') + url = request.args.get('url', '') - r = jsonify({'data': comments}) - r.status_code = 200 + logger.info('retrieve comments for token %s, url %s' % (token, url)) + for comment in Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).order_by(Comment.published): + d = {} + d['author'] = comment.author_name + d['content'] = comment.content + if comment.author_site: + d['site'] = comment.author_site + if comment.author_email: + d['avatar'] = md5(comment.author_email.strip().lower()) + d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + comments.append(d) + r = jsonify({'data': comments}) + r.status_code = 200 + except: + logger.warn('bad request') + r = jsonify({'data': []}) + r.status_code = 400 return r diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py new file mode 100644 index 0000000..2d91635 --- /dev/null +++ b/app/helpers/hashing.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import hashlib +import config + + +def salt(value): + string = '%s%s' % (value, config.SALT) + dk = hashlib.sha256(string.encode()) + return dk.hexdigest() + + +def md5(value): + dk = hashlib.md5(value.encode()) + return dk.hexdigest() diff --git a/app/services/database.py b/app/services/database.py index e3baf82..f0fc403 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -1,15 +1,13 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import hashlib import config import functools -from config import DB_URL from playhouse.db_url import connect def get_db(): - return connect(DB_URL) + return connect(config.DB_URL) def provide_db(func): @@ -21,12 +19,6 @@ def provide_db(func): return new_function -def hash(value): - string = '%s%s' % (value, config.SALT) - dk = hashlib.sha256(string.encode()) - return dk.hexdigest() - - @provide_db def setup(db): from app.models.site import Site diff --git a/config.py b/config.py index 351dc1f..00c8647 100644 --- a/config.py +++ b/config.py @@ -6,3 +6,5 @@ DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 + +SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 8610f98..600ab6d 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -17,6 +17,7 @@ for path in paths: # import database models from app.services.database import provide_db +from app.helpers.hashing import salt from app.models.site import Site from app.models.comment import Comment @@ -57,14 +58,14 @@ def convert_comment(db, site, filename): # create DB record comment = Comment(site=site, author_name=d['author'], content=content) if 'email' in d: - comment.author_email = d['email'] + comment.author_email = d['email'].strip() if 'site' in d: - comment.author_site = d['site'] + comment.author_site = d['site'].strip() if 'url' in d: if d['url'][:7] == 'http://': - comment.url = d['url'][7:] + comment.url = d['url'][7:].strip() elif d['url'][:8] == 'https://': - comment.url = d['url'][8:] + comment.url = d['url'][8:].strip() # else: # comment.url = d['article'] if 'date' in d: @@ -86,7 +87,7 @@ def convert(db, site_name, url, comment_dir): except Site.DoesNotExist: pass - site = Site.create(name=site_name, url=url, token='') + site = Site.create(name=site_name, url=url, token=salt(url)) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: From 809d46730e46fe844ad530e3c17e2c62f524b2b1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 4 May 2015 20:01:47 +0200 Subject: [PATCH 025/586] SQLite compatibility --- .gitignore | 1 + app/controllers/api.py | 3 ++- config.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index faac339..a590a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ target/ # My ignored files and directories myconfig.json +db.sqlite diff --git a/app/controllers/api.py b/app/controllers/api.py index 287712f..50004b6 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -26,7 +26,7 @@ def query_comments(): logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( (Comment.url == url) & - (Site.token == token)).order_by(Comment.published): + (Site.token == token)).order_by(+Comment.published): d = {} d['author'] = comment.author_name d['content'] = comment.content @@ -35,6 +35,7 @@ def query_comments(): if comment.author_email: d['avatar'] = md5(comment.author_email.strip().lower()) d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + logger.info(d) comments.append(d) r = jsonify({'data': comments}) r.status_code = 200 diff --git a/config.py b/config.py index 00c8647..9777272 100644 --- a/config.py +++ b/config.py @@ -2,7 +2,8 @@ DEBUG = True -DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +#DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +DB_URL = "sqlite:///db.sqlite" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 From f29a53be94841a59f8ad7b7744fbab6882674f93 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 4 May 2015 20:01:47 +0200 Subject: [PATCH 026/586] SQLite compatibility --- .gitignore | 1 + app/controllers/api.py | 3 ++- config.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index faac339..a590a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ target/ # My ignored files and directories myconfig.json +db.sqlite diff --git a/app/controllers/api.py b/app/controllers/api.py index 287712f..50004b6 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -26,7 +26,7 @@ def query_comments(): logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( (Comment.url == url) & - (Site.token == token)).order_by(Comment.published): + (Site.token == token)).order_by(+Comment.published): d = {} d['author'] = comment.author_name d['content'] = comment.content @@ -35,6 +35,7 @@ def query_comments(): if comment.author_email: d['avatar'] = md5(comment.author_email.strip().lower()) d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + logger.info(d) comments.append(d) r = jsonify({'data': comments}) r.status_code = 200 diff --git a/config.py b/config.py index 00c8647..9777272 100644 --- a/config.py +++ b/config.py @@ -2,7 +2,8 @@ DEBUG = True -DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +#DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +DB_URL = "sqlite:///db.sqlite" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 From 4a8cad8a003e791c1904369ab3a090025c177a14 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 6 May 2015 19:46:51 +0200 Subject: [PATCH 027/586] Fix SQLite date sorting --- tools/pecosys2stacosys.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 600ab6d..f68873b 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -5,6 +5,7 @@ import sys import os import re import logging +import datetime from clize import clize, run # add necessary directories to PATH @@ -34,7 +35,6 @@ logger.addHandler(ch) # regex regex = re.compile(r"(\w+):\s*(.*)") - def convert_comment(db, site, filename): logger.info('convert %s' % filename) d = {} @@ -69,8 +69,9 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: - comment.created = d['date'] - comment.published = d['date'] + pub = datetime.datetime.strptime(d['date'], '%Y-%m-%d %H:%M:%S') + comment.created = pub + comment.published = pub comment.save() From a571a78215c3b34b6e65880af65d35b76efb1bf9 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 6 May 2015 19:46:51 +0200 Subject: [PATCH 028/586] Fix SQLite date sorting --- tools/pecosys2stacosys.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 600ab6d..f68873b 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -5,6 +5,7 @@ import sys import os import re import logging +import datetime from clize import clize, run # add necessary directories to PATH @@ -34,7 +35,6 @@ logger.addHandler(ch) # regex regex = re.compile(r"(\w+):\s*(.*)") - def convert_comment(db, site, filename): logger.info('convert %s' % filename) d = {} @@ -69,8 +69,9 @@ def convert_comment(db, site, filename): # else: # comment.url = d['article'] if 'date' in d: - comment.created = d['date'] - comment.published = d['date'] + pub = datetime.datetime.strptime(d['date'], '%Y-%m-%d %H:%M:%S') + comment.created = pub + comment.published = pub comment.save() From 4e6c59362cc32a5f42b7d4cb133561bd7fd753d0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 9 May 2015 12:51:14 +0200 Subject: [PATCH 029/586] Add CORS capability to Flask Add static blog demo files --- .gitignore | 1 + app/run.py | 4 + demo/app.js | 14 + demo/package.json | 15 + demo/public/css/font-awesome.css | 4 + demo/public/css/grids-responsive-min.css | 7 + demo/public/css/pure-0.css | 11 + demo/public/css/style.css | 176 ++ demo/public/fonts/awesome/FontAwesome.otf | Bin 0 -> 75188 bytes .../fonts/awesome/fontawesome-webfont.eot | Bin 0 -> 72449 bytes .../fonts/awesome/fontawesome-webfont.svg | 504 +++++ .../fonts/awesome/fontawesome-webfont.ttf | Bin 0 -> 141564 bytes .../fonts/awesome/fontawesome-webfont.woff | Bin 0 -> 83760 bytes .../OpenSans-Regular-demo.html | 364 ++++ .../OpenSans-Regular-webfont.eot | Bin 0 -> 19836 bytes .../OpenSans-Regular-webfont.svg | 1831 +++++++++++++++++ .../OpenSans-Regular-webfont.ttf | Bin 0 -> 38232 bytes .../OpenSans-Regular-webfont.woff | Bin 0 -> 22660 bytes .../OpenSans-Regular-cleartype.png | Bin 0 -> 84913 bytes .../specimen_files/easytabs.js | 7 + .../specimen_files/grid_12-825-55-15.css | 129 ++ .../specimen_files/specimen_stylesheet.css | 396 ++++ .../opensans_regular_macroman/stylesheet.css | 12 + .../img/308a3596152a79231f3feedc49afa4ef.jpg | Bin 0 -> 3618 bytes .../img/b133b66b7edc9f7ffb5cf74a87e63652.jpg | Bin 0 -> 2637 bytes .../img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg | Bin 0 -> 2669 bytes demo/public/img/framasoft.png | Bin 0 -> 19259 bytes demo/public/img/id-150.jpg | Bin 0 -> 19264 bytes demo/public/img/license.png | Bin 0 -> 5460 bytes demo/public/img/planet-link.png | Bin 0 -> 13013 bytes demo/public/index.html | 208 ++ demo/public/js/cors.js | 46 + demo/public/js/cosysnode.js | 43 + demo/public/js/markdown.js | 1740 ++++++++++++++++ demo/public/js/mustache.js | 586 ++++++ requirements.txt | 2 + 36 files changed, 6100 insertions(+) create mode 100644 demo/app.js create mode 100644 demo/package.json create mode 100644 demo/public/css/font-awesome.css create mode 100644 demo/public/css/grids-responsive-min.css create mode 100644 demo/public/css/pure-0.css create mode 100644 demo/public/css/style.css create mode 100644 demo/public/fonts/awesome/FontAwesome.otf create mode 100755 demo/public/fonts/awesome/fontawesome-webfont.eot create mode 100755 demo/public/fonts/awesome/fontawesome-webfont.svg create mode 100755 demo/public/fonts/awesome/fontawesome-webfont.ttf create mode 100755 demo/public/fonts/awesome/fontawesome-webfont.woff create mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html create mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot create mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg create mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf create mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff create mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png create mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js create mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css create mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css create mode 100644 demo/public/fonts/opensans_regular_macroman/stylesheet.css create mode 100644 demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg create mode 100644 demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg create mode 100644 demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg create mode 100644 demo/public/img/framasoft.png create mode 100644 demo/public/img/id-150.jpg create mode 100644 demo/public/img/license.png create mode 100644 demo/public/img/planet-link.png create mode 100644 demo/public/index.html create mode 100644 demo/public/js/cors.js create mode 100644 demo/public/js/cosysnode.js create mode 100644 demo/public/js/markdown.js create mode 100644 demo/public/js/mustache.js diff --git a/.gitignore b/.gitignore index a590a7a..6328437 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ target/ # My ignored files and directories myconfig.json db.sqlite +node_modules diff --git a/app/run.py b/app/run.py index 8c288b1..0a5e674 100644 --- a/app/run.py +++ b/app/run.py @@ -5,6 +5,7 @@ import os import sys import logging from werkzeug.contrib.fixers import ProxyFix +from flask.ext.cors import CORS # add current and parent path to syspath currentPath = os.path.dirname(__file__) @@ -45,6 +46,9 @@ app.wsgi_app = ProxyFix(app.wsgi_app) logger.info("Start Stacosys application") +# enable CORS +cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, use_reloader=False) diff --git a/demo/app.js b/demo/app.js new file mode 100644 index 0000000..aade06b --- /dev/null +++ b/demo/app.js @@ -0,0 +1,14 @@ +var http = require('http'); +var finalhandler = require('finalhandler'); +var serveStatic = require('serve-static'); + +var serve = serveStatic("./public"), + port = 9000;; + +var server = http.createServer(function(req, res){ + var done = finalhandler(req, res) + serve(req, res, done) +}); + +server.listen(port); +console.log('serve static resources at http://localhost:%d', port); diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..5876a71 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,15 @@ +{ + "name": "website", + "version": "0.1.0", + "description": "Web site", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Yax", + "license": "BSD", + "dependencies": { + "finalhandler": "~0.3.3", + "serve-static": "~1.9.1" + } +} diff --git a/demo/public/css/font-awesome.css b/demo/public/css/font-awesome.css new file mode 100644 index 0000000..c0a5993 --- /dev/null +++ b/demo/public/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/awesome/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/awesome/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/awesome/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/awesome/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/awesome/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} diff --git a/demo/public/css/grids-responsive-min.css b/demo/public/css/grids-responsive-min.css new file mode 100644 index 0000000..7bcfd32 --- /dev/null +++ b/demo/public/css/grids-responsive-min.css @@ -0,0 +1,7 @@ +/*! +Pure v0.5.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yui/pure/blob/master/LICENSE.md +*/ +@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} \ No newline at end of file diff --git a/demo/public/css/pure-0.css b/demo/public/css/pure-0.css new file mode 100644 index 0000000..14497d9 --- /dev/null +++ b/demo/public/css/pure-0.css @@ -0,0 +1,11 @@ +/*! +Pure v0.5.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yui/pure/blob/master/LICENSE.md +*/ +/*! +normalize.css v1.1.3 | MIT License | git.io/normalize +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} \ No newline at end of file diff --git a/demo/public/css/style.css b/demo/public/css/style.css new file mode 100644 index 0000000..230b848 --- /dev/null +++ b/demo/public/css/style.css @@ -0,0 +1,176 @@ +@font-face{ + font-family: 'open_sansregular'; + src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot'); + src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot?iefix') format('eot'), + url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff') format('woff'), + url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf') format('truetype'), + url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg#webfont') format('svg'); + font-weight: normal; + font-style: normal; +} + +body, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"] { + font-family: "open_sansregular"; + //font-size: 14px; +} + +h1, h2, h3 { + font-size: 33.75px; +} + +a { + color: #045DB7; + text-decoration: none; +} + +a:hover, a:focus { + text-decoration: underline; +} + +i { + margin: 2px; +} + +pre { + background-color: #F5F5F5; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px 4px 4px 4px; + display: block; + font-size: 14px; + line-height: 21px; + margin: 0 0 10.5px; + padding: 10px; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; +} +#banner { + background-color: #EEEEEE; + min-height: 20px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; + font-size: 20px; + font-weight: bold; +} + +#banner a { + color: #666666; +} + +#banner a:hover, a:focus { + color: #0099DD; +} + +#banner p { + display:inline; + font-weight: normal; + margin: 0 9px; +} + +.l-box { + padding: 1em; +} + +.tag-1 { + font-size : 12pt; +} + +.tag-2 { + font-size : 10pt; +} + +.tag-3 { + font-size : 8pt; +} + +.well { + background-color: #EEEEEE; + border: 1px solid #DCDCDC; + border-radius: 4px 4px 4px 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; + margin-bottom: 20px; + min-height: 20px; + padding: 19px; +} + +.title { + font-weight: bold; +} + +#sidebar i { + margin-right: 5px; +} + +.nolist { + list-style: none outside none; + margin-left: 0; + padding-left: 15px; +} + +.label { + background-color: #CCCCCC; + color: #FFFFFF; + font-weight: bold; + display: inline-block; + padding: 2px 4px; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + vertical-align: baseline; + white-space: nowrap; + border-radius: 3px 3px 3px 3px; +} + +.comment { + clear: both; + display: block; +} + +.inline { + display:inline; +} + +#footer { + background: none repeat scroll 0 0 #EEEEEE; + border-top: 7px solid #000000; + clear: both; + color: #444444; + margin-top: 30px; + padding: 15px; +} + +.divhidden { + left: -9999px; + position: relative; +} + +.preview-markdown { + border-radius: 10px 10px 10px 10px; + -moz-border-radius: 10px 10px 10px 10px; + -webkit-border-radius: 10px 10px 10px 10px; + border: 1px dashed #000000; + background: none repeat scroll 0 0 #a0bbb6; + padding: 10px; +} + +.button-success, +.button-error, +.button-warning, +.button-secondary { + color: white; + border-radius: 4px; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); +} + +.button-success { + background: rgb(28, 184, 65); /* this is a green */ +} + +.button-error { + background: rgb(202, 60, 60); /* this is a maroon */ +} + +.button-warning { + background: rgb(223, 117, 20); /* this is an orange */ +} + +.button-secondary { + background: rgb(66, 184, 221); /* this is a light blue */ +} diff --git a/demo/public/fonts/awesome/FontAwesome.otf b/demo/public/fonts/awesome/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..3461e3fce6a37f2321ecbe64707f04c0a4f05424 GIT binary patch literal 75188 zcmd42cYG5^*C@QQyeoNEmI+v3OJ1!hp?BN#Bql&0F(rUQ=*C61jEjsU_uhM#yN!)a zZ=nSOfy5~U0x2Yzbn+xmdPp$|WF(Ia_sq&BJV=l2St!J@u6Ld}hLIigG(oNmSNVYp2bu*`8&mCT(g%JrKs|W6VjPjD7@?@<57`EsF_Gij(~7S;$jX z2uy5njLz_R|~-giuu0_{g+f+ve88OQ{Hz6>Y}qGm4HH8LdoEP_Ir~wHA13xKq37E z1Q7#%ImkKEQfdVC%s|@tAvjG9rGf|G%tLS)wVE;wz~z*JdUGJ{Lb24ffiy>{sLuw{ zN=i%p4&x(nc7ggcB(4K#2{l|&I*@jvl#*QoX(=^T^1?vc`5_#d8Y8(m0B0V8%cxE# z$pBnVc{p_qq+KX?r%B0{5Tf&5n`|=c zVocso$A%h=aRy_sSr<0ddtr36w}@);HtI||V*&u5GQ^q}ChAOv7#*33dEO5J<`I%J z*XfL=NJFf*@6;JnrxS?Jv(dU>lMZNv>x|xJgST0)^ZvUTCS9nR;D;OFCALM<4|cS0 zYNX*m0;fd-nOKu<8nuWrP;pc;Wuzjh2ue?xfq7<9)2SJhFQgVyVemeL(m{GHV42{( zj*5ZUn|hjxr9$DY5z3R_VDViTHB~GZO+`ceH&s%?2xUzWj8p>r63nNdWGs|hNF|Ez z3_x2)t$`3h#RG+4z;(3FM9l*V{~4dWakP0RwGPS}p_WLIvN!Z%D)eP4^k@*r2UcJ- zU?=@*H?{x2(58Ba^p5QH?|rs+TW>_~_TISOtlp~Lj^1Ov-M!a( zzv}J1P2C=H+Z$+4xIO*0|LtdQZ@V3LJL`7U?T*{WZg=0lcKa(r`~KGyb$|2y)%O?P zpZ?_!U)FuOW{B$$#SnIg%Mkex*jk1-50MUW8X_4&x!rfW>vqTOPq#nZZoBom{jU7N z?Kihy-F|Vq<@U4NPi{ZD{owY!+jnl?x_#sJZ|;=H59|LA1MdHSc=vyM#lTJ^gTau`vLeC!n{ysFfXP_Uc`a;;df9b8Q`%5CI-q;j?f_Z z08@+f2t13LIfyd|dpIWbJ7SE2M+X6Y`2Iv zkp55o6=8%9;E*zdF@cM1gm+?lAU^<05&JWMOK=9?GfrY#nxQ=#37!98@E7s2C_kX( zC)VL8>pEDTzy|wG(u4WIx(CZZyip8U549cAFn<07M;srB30*Ni03+$ax};f-cVgcG z?mU=>^dM|7CT$V}dFOaPnef&?TC8tyti(D1AN0WcgLMzq`5)sfN=5Jn`%Q2L%ZV|e zl|>C1nDg+#cYyEwFueh|8;M1@qnrlv{tx2;EpI}L@Bj%;S(HBnvCo4r5Z3J3VAh|L z<;C|91Fq}f+8ik7{a@>YGGgBWt|0H6vm9+D_>zG%!GU*vgSez_v3`gN?0**q@gSZe z&4DsfsLMf%#B&~$%c2BmvjBC70pNldvK)OGz|)9*7$^_8{)72JI)izrZzX|2bz&g6 z5X63xu^GT)2Fno{M$)8hgZ%>hi3CQMO{9n`r5)Xz4^*h=``X=^^&>Ji>7z6GQLVJL| z_aKddx*yOSg#T~iaf59p@jw_#VuBCxViH3?=0bWXsoR0$7|#Km$Kk!l!}JNx|I;6t zQ24u_Le9kh{ZB$TOd^pe9yTF>YR?YaZFd!x{0fp_1!PlmxoWQIAlbaCRI;O$No?ms&tQPAvhI zcLfzlZKi@i0oH?3y#tirbSj%Fr|PH{s)IU29j9KRy5UrMnfj3Wg8G5#rT(J&;SlaD zkxPb1R1%Fukc^g0koZa-mn@S6N`fTOk~m4Kq)<{PX_a(J4ogl*PD{>7UX{Elxh%OM z`M2a}$#0TBB}j75iFF$8q;VSOd9(^983P8*!UoeWONP6bYtPEAf-PDhGoA5e9%B|W&ob+o%}g+} zor!0%nS7>*zlX3A#CmdZBB z^s;1GuB<{3O7?^7SJ@vjyIdjH$RCx@kk69Omp?9FB3~hY zP97?ck{jen@yA4hRgPR6M?4L6qa z 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�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 DgjuED literal 0 HcmV?d00001 diff --git a/demo/public/fonts/awesome/fontawesome-webfont.eot b/demo/public/fonts/awesome/fontawesome-webfont.eot new file mode 100755 index 0000000000000000000000000000000000000000..6cfd56609567bc9db55186415c694d1d32808fc2 GIT binary patch literal 72449 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
U~M!&>g0b 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 literal 0 HcmV?d00001 diff --git a/demo/public/fonts/awesome/fontawesome-webfont.svg b/demo/public/fonts/awesome/fontawesome-webfont.svg new file mode 100755 index 0000000..a9f8469 --- /dev/null +++ b/demo/public/fonts/awesome/fontawesome-webfont.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/public/fonts/awesome/fontawesome-webfont.ttf b/demo/public/fonts/awesome/fontawesome-webfont.ttf new file mode 100755 index 0000000000000000000000000000000000000000..5cd6cff6d6f6cf438a882e366420dbcc5dddd3f1 GIT binary patch literal 141564 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ƓCICn66&u!PHl6pn& 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%0D%8GQ!Ai?6y^fVUY%4hi>cI9O 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
KT23~?3Dw&p)A=062yj$!;@;Sv13 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 + + + + + + + + + + + +Open Sans Regular Specimen + + + + + + +
+ + + +
+ + +
+ +
+
+
AaBb
+
+
+ +
+
A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
+
+
+
+ + + + + + + + + + + + + + + + +
10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+ +
+ +
+ + + +
+ + +
+
◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
body
body
body
+
+ bodyOpen Sans Regular +
+
+ bodyArial +
+
+ bodyVerdana +
+
+ bodyGeorgia +
+ + + +
+ + +
+ +
+

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+
+
+

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+ +
+ +
+ +
+
+

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+ +
+ +
+ +
+
+

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+ +
+ + + +
+
+

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+ +
+
+

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+ +
+
+

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+ +
+ +
+ +
+
+

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+ +
+ + + + +
+ +
+ +
+ +
+

Lorem Ipsum Dolor

+

Etiam porta sem malesuada magna mollis euismod

+ + +
+
+
+
+

Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

+ + +

Pellentesque ornare sem

+ +

Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

+ +

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

+ +

Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

+ +

Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

+ +

Cras mattis consectetur

+ +

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

+ +

Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

+
+ + +
+ +
+ + + + + + + + + +
+ +
+ +
+
+
+

Installing Webfonts

+ +

Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

+ +

1. Upload your webfonts

+

You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

+ +

2. Include the webfont stylesheet

+

A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

+ + + +@font-face{ + font-family: 'MyWebFont'; + src: url('WebFont.eot'); + src: url('WebFont.eot?iefix') format('eot'), + url('WebFont.woff') format('woff'), + url('WebFont.ttf') format('truetype'), + url('WebFont.svg#webfont') format('svg'); +} + + +

We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

+ <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> + +

3. Modify your own stylesheet

+

To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

+p { font-family: 'MyWebFont', Arial, sans-serif; } + +

4. Test

+

Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

+
+ + +
+ +
+ +
+ +
+ + diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..6bbc3cf58cb011a6b4bf3cb1612ce212608f7274 GIT binary patch literal 19836 zcmZsgRZtvUw51zpym5DThsL#WcXxNU5Zv8egL^}8cXxMp4*>!Rfh5d-=k3gW1;PMQVF3RzW%ci{fFmPHfCS@z{{K`l z41n@~^u3v|;D7Xg7dAi*;0~|>xc(Q?0$BW~UjGHq0h<3YJAeWd?h+ZWM9EYu5@Hs0EOnnkAtTzP9coXJALmS|h&nzJd% z7?C@cPUEGrLHk-#NysfAePe#dP9_6D5VGbo4fVVs0)83}G7LoWV`e*{V_8RPK>Iqw z*X0)8;uQ6FzC+dip(fgJU!9*!>pW6;pdJ$jHReX|0V)o@BosG=sN|PYN^-JAOY{e4 z&QjmR91WNK#}_%Ei?QhW{ab*7Eg=}E)Ft4XeyVhoR4<|byJf1$4VGsxP`9bNBp-((Wawhx zlK;u}?+b5Ii!k>ELIS zPOH%u!jQg8T>Z_#S%<^^|CcOH?XN>$IX|aEQjBic^$pg1`=0Y3Q(mv* ztDZ~~0GdAF>L|BQmHQ*s3r;T~(0;3p;I?%VHpGPt-kXLE3iel2aEIYw5<*Tu6)mB2Zdp4#k4Oz!8SUkT&;Qte`Iq~*4U zD>qT9mSnB=3s~xUgo_vYp#API=~%dKiKqTMXWvn)p~21nSE!cT5SsJTu)R?b1p!+K z!OU2E?^HE49L>c*z)KLpsv9>&-7AKaYlMAztV}6vISI-rtA6=8k`=+S>+C0X22_El zG+i&#b34h$o{gdGZ$>$81)ovjw6Nn76?gBhm&(oX%Gl7C`RDCRpH0f?NEokA^!>;1 z%KC0rbxWq(b)XGCuDPUgvx=VFeE!Yhn7tF%LI~H+p>549%5AqnPWWvF870oRi}Ig6 zBdaI{Fa=dRbLL@+G zt@VO%=$Om*EulLy$6I72!E$J{;p zONB3HLoKgq^6jJF(Q`)L`!cZ+Rr3W%j$jUFFQ>qTy9U3hZ4h|+TM+XM0=d);0+WP* zH3@dm#w7zwp0FtidDmt@7NF1}mU4P$EY|Wkj4mH3R0-KSyk}mz4A4$XnVzGU1ny;{ zr9K{Wq#=h@cd(g4{+b*Qi^ZU3gD1uJhMpP)`|4#)S7%CUD1V?qjVHn4L!j5zA}ut& zDHYpt7rryJOpQZQcQ??@EKS$QO8W$u#LG?i4dgC}^LsmrmVoh-0>Cp<6C#oePz@ic znc{A(*xo*}Gg=DUR{sWZO2O!S=0$cJl7by8{!t-+*TZ&T9bbJ7wa2)MA?uM1^}3pD z!Mnm7PnG9ji{zTSNtd|?oe?d4$WpWLW4dMJVHy7D6t6X`N}z*zqg8B$JmXh6AP)aX zx4a+uFaSa*g>S$NC3TbnlQ^&r0ToUZAvLgxBh<1THf>}}Ts{7zD84WCblCDox?M#`(f%UZNrShhw|$nZN-MhhQP+c9hQHAgGJ_IV1b6^2F=- z?fhtv>A1W^6@54mjz5;7t*eptF`~4*cKXD!5$8W)UW}qW-In5GvPn;l{`(-SB7%7zGad2Yj6(!|Yd(VI^ zC&ZiZE>|fAm1H4v7inHh0gbSXh9;d3^mP3F9aj*xVgTHvzV&rhAm#ZR@sy6HY+57} zeQrb@_!T>7O|l5W&I8EJk4PD+eu7{9fix|s50>4l<-?he4QGVD*`Wl}V0uT=;4nY9 zEm;IJTr)#{>0^c~9uJ7iFJp7d=}N}i50uIDTAPbS1r`Kew4)^8WcXFFN4I32xs6b< zM&&#yNQ)TAU!+&2w1Dp$`K)N4lwMf`e_{ncP9W&odNN_CQ>@#pvQ|mh$&8I{E#bl> zB{VRuj9O6?c8!sDjhgs5*MQE6OxJ83X+X`AI_G)kQew9Ci-&)8eq=7sNlRp^bIxEQ zg|HclB2$$1v8c0Wisk@^O2sd2(kXv7=Ek#Wb8SVE1(H9H$$OHV^iX=5ZwM=Pu02e89|at zbFfF)-U0D3q8L$vmV7d@9I_-tBZ=NZjrKjDDP1X`vP+F--+M2*vuCD^TJ&x$t+uqT z{gy!y{@6Tm=L znG~jgC)-NfHfDLrDM=uoHZM=BNVmK{Pe(M(RjT8*-;1b0XSnNA4?|eUJqsD)D)@}; z{CpywKAqMb9wZ(6Y~4v3R-)tP9!E5UYUGBA5QC#xIu11gw%N*a*Q8(2M!m|E=H27^ zZXFt9A*oM7qF3D|Vt(Kk3UuS_L?(%S$5+s_seNGFSQN>aT|4Kk!7e7pa-zOiWG5|c z9*LIZxA-x!0O~*=M&|Ask{QPsIKK+<*}x{ZpPV@RFv0}Cxy!_fQ5O%boHd;%F?A!I zO5Q3|OR+`Cag+~w)1E`G!l8k?0rG9pOi!bU>Nj4|dc0g^TCPr_d(JY#_j4NZwiEyY zad+EiOP~qG{re_HT!Tu0b}9m&-+EnjeHax=I0qqe8wB6WTvwsvvc>M%#>dW980a;2 zMVnq%$yM7!W$r6;h2PBNLB!~Rfh|Z-k(5|?RbP-d8v>mau#JQf#7N;F!=a*C;qCy? z-m2K+j18jpX{S=OH5CGrQ#tkR&98;#oJ5MO+Z2@HIhCZe9J-ooRY{5V4N2VqE#2+mpdE}`C!1{}3U?V2V*Cw6Z>cq&a?X6gN(o2l1eaxDB zZp*{cNN;-(ALedD2XqzE89oT3lwo4=3mXEO*jLdO;tIv_q~k}02M&l{usI;}&@iUz zS};fwOPs4NxW-!BNaCWH?9w7-4k@XNVd5jN*`mdTZQRL6xF(d~cf{E$>60g9qm~}Y zo7$|>Jg_GaK?QkIjVIX6JktAcoEf>akVgU zWSWB@uUgK$ipXjs88B*f2>-^rktwrEXY&}L*onyN5S?Zl2}fWO%usD4O$9u{&mgWL zP>D}i8zKqYtdn#5(zA?O9K6f7SI0}a;RPGsZ{G)MVvdyUK55Gb7vW-S)bR572CP?b za}s;<5HMCsc1n&o(w~fCN%MLk+{Yo2x*$8G91S&vvII6dWWkg-7FUf&Y? z9a_&9hO?#ZUpRyL_MID@2}}j)E_FG>pa1$+&PWrcPSnWvfu}#_QPg_Nx=~*Hnc^a>lUicEr6y*?-!uaoR-ZkCvaM>bWQNB8YB&B0oyeY2FKgtn%Mx|B|zGtOO1xCMaIm9^>Fp z|1Zg8OMJ9}eN{aF3gzDii(~7!d|(Za0-`;2k%0_;ZYFVCxV_h^Z`S-Qr|J?3@e{Bp zWBK#47K$Yk)?@m$)2Q@24WltBwoOG0=` z@y25+2eUMkxw{C4muMZPmuIalcyZHmwYd1)B_%v}UX70wk|SH>5SVaaxUD;o@Dhcd zh|FNgT%rNB>;WzIlk_BtC5QT>=H@A3%zvd6fyU|_QtC%GbeFenirHKlnE+3UCz2cS zk;eR6X486;dzQQ*fR3!(Nh;MRJ{bSHddVHbMq`(MVV%4ojZ;9K@Btr1 zb&lxztBj%mYk@aVL;7;(v{QVF7HXojz~*}pj2?DmX~(V(#+08OeJ zhm=J|GYGwXImQ+yP_H8Y7I^9%H3M=rIWD285Gfd_$Fs6g-&4TN%3y&_2;W0Zgk}?w za_=6sPZ)r-$*f_hY`k@=Ayu>ng@d#DTXZXv@7tq;l^n^-4L&Y(M|&?5enQ=r16|$p<#N$V zGU`*|0teb@D;665)nY&vB9MAqupeY5=L?@rVjLSO~G+B!0t zm${EyNFQnV=DmK*%;_DrL%M2Do309pBq|<}a$zU42h~&usMl~SBu?9&+rk_=74cQT zNV8{uni!(;sxMT=@Aj)b(6z9^hi-WTF2)J4%-4c^LK$#bcfOaKYdpP^kf|JyHNn}I z5x>SC_yMRhQ`0u`nPp~B=t>&gGk;%$c%N8k@8N%$iD@4a!%(|(C9~zX_v_sTox}sT2FIn(x96wW|MzH>Z{$K+l@aG}8 z6emVN+jssSjniGZmXNPZFtVI4TBfB)_LyEv6_EK6Ls^Fiq+Is{ZZ3K>b*7~W21#}9 zJnFv%kbM7`$-~!N(d}_e)dO(jo(KsJlKze{>Xl({HqB9Y4T;k2@Z>};t`hD1DmDC! z3T6A<3lKNJL{T;eovS}lZp@1AxubzxSE+UuV$d|QW#k!x;H}TvqxXL&KD1M^9Q%He z6ZgH$h5>Azg;)s2sFnX@8vfu^vG+65Lhfb}t)iMB+XuUzefy&Htz(>7Lm<1?o=E{4 zqX&6#ZqO$13oQZbYjF#N)sLcNDrR67tPVY12MNsIb{<<)r!`6RZ2W|!Z8tCieo|33 zi1qv~T-j_0iW0s!NG^i0x2yQ%t)MVp0}bG#2ekg%oXooKzG6ut zec^f);@(EShH;OOYpZ+dLn(GM@`1x8GOmIsf>Ma+_7 zGmm|(C0ZbVC5ewJ(d<6^76s=Pz$)?c)GW8lu@oqkY47A!;P*8s!q3_RE%j0npP+Fi zu15RnsE2SDZd<6n|Z1F%S ze?Hl_XAf<7|COS&hj$ffTe!u49A?doGv1Qrv;5%FrxC63;QH~{jnKtZjdEq~bVAjk z+9pg(>Q_D_BW6l_iw#1?r({A3oHB#c`u8GgZzDjH&jN1LCDR(}O~bL7ZZaj_`a)0Z zyV74I4-+j}<)#Cw#d}|WCHz84q-zbWV3fxsgQ3-cIV+>z#|FW%gLQ`rjv^+yZBXnU z)2Z74=G=FolM7RW3~PCvffhenR+hPrb>;7UpH7&~(`n(UeY&4nhcKZf+Q-p-Sb5|W z(>ycw=5m7Xyi{jwK5kQwOn$R*i!~L$RiL*hmj-gNBcCplXlk^3GsdUpQF<4IheJE@ z6TYI7vr#FNf-2tM5XjcD1QJ|#h$`lmCfpYVv?XNN%Ag(67E}~t<9|!V2#vZY*UALQ zWf;z|hzP1gj#Gyqjx}lKNP=h`o}{4*_)*CJ6waG(g)uqPjRabn8aMcq)?kdhD}>jsQ)C=kk5O*e zqvnQ#3|V4k1?inmPEB69MjrLUifnrLxp;6N%`+ZG-U(r^b`fphQXkyna z9$|Nt1-^D-q!*mN=E`_fr}nlVBUpuy8#$EcZs`D3kdW&3pr=0@4xC$G!+A9Z$ z@~9vnLRWykpS9^XMK&gn8tg!~7SQw=zdw;&ibQ}lo~#6WDfy5}AvE1wm8`77Bd+2c znGRGYpWKaPL~I;BQ&0}i)Mq){(}mCj39Yq+668S}qY$+%F1f?km~mJ%t?)HdhOEy$ zEB;>Cw?uBDq~}m*pcX@m!-kBc3xG1Yblce0N~^Dsp&%D{gPqSJ1+JkL{j)|u!%%yI zyr4k{xTA(cxIXf7&ckTQ16STp7Auz16ZHhvTH1xuK<>&M6O$qc%Ua>sgtDU!3ogas zWKpyQjywXw46+(qb%#lbpo=HIb}zCyOEV9ro8Uc#&H`(_9dZZa>(9rDO{X@pjj>?E1r%zqv_Nw7(|wg1nvD(eI}a zY1qR9g@+Tu$aVk>BqD=82o9lKelCRU)1mT96r*K~aBAOT23E}m8|YE!iWo@QM-ybs z@F&)c^c=1|!lO(lxXWt>qjMKCBNmhCR90j{Ijn=a0Y==3q@HnkFWP|}RcKbu61sAT zSIyEPfbM(RQVdo{!;gtBqeBkuv1tY~mrafxO+6^1)tH}voDB3ec!O=8(f{WQQPMJCxpXPS8bZJa4`LieuX~<<&FA=Cv{tCj< zD$Z2nXKYL*Z$77+;s9oF>i!O{+YaWV98uiL2g}$o{5d4N$`#zCLDQwcH|vs`wuI%E zeVPG1Smv-FdsGelNDPio#3^|~^)+HEW!_Lr!%HjL4}Wc+X4bz=J1%IKw&JwPqaODS zW^a}yt9ma_{h|vz`P@x!X}~;k6^7%k*#SYUKDj>i{Fl?W!=GAz^cI~)g1x4wJT86U zhO1OlAuaEWU3SDlR5J7M&e$aveB3~3%_d1Pl8AG(0g7mzf;ET%w+!Hp-TB}Guz1Y; zs4|*{y3Vsu9k?G;k;EHhreUIm<&l*Y=cQr`n?mA!xqLv_9>S>W@M!6)lRwc%l6{h!X@Zkfgu|qQQ z+~C`oDuTrdU)GT6T(dU$@O*X_7_NZSznB1@R(6s9)#bz`v`Jg2HOeM2)Y&29nH?H# zO!q~3Xj>}Y@F~kpaOPal+thT*YnCc04F%vd8K3CasF+=6eUFOU)GS7I49y(_G`&?( zT;2F?ddsl9Vd=i&gqdsf{WUN666Ly#?~TzY^$YU8d!!a%kNK4{;co5&7)a1%Yy0sm zA1SQBBKQgVLb@FdK8T}kVX}$*D(N=6K;PuI3@4mr=?VRS^$id;{JdIjKf3i0BE4$8 z^8!hVXBGT3F@7)ob;`%gI3I|aM^plWDM8!kboqBkU9l|5UIKXz?}IJ8jV?0!grb9} zQpH1fO^jbE=C2Jwxev7>wvCrp%C4=D&RDyto{Rsp(S2qyiyPqLvO9OuKKIv8i+Lam+9p&%+e#Pbb=LzUxuIB!;j2{cG(cs)7 zhD1-Qu6E$hq+L;Op*5POg13v@0Ek7$S=7_Q862gfOMUUscusILHDiP`U8SCJFY-&& z1>2-~{pT;Ca6ZsqeKI!>KtHm;HZ!f}l?Sq?X@2J}MbH1;smyYrEfg|0@2W`>V~o0F0l^%&kdWZ~4K?%Uv*Dbu$zR`!b*8my%6Y0EgdQd5 zjL>9Il8==%v?Mq^5q}*h=S-CQAb4Z4AxJEg%TK3>5PfCt44^X_tsc}yMW0Gb8g)F6 zuKV1BG z44?MR&tCORGEDPd9u3%!pUH+k7Qdg%jfGo$fQCf9{Mi=hIlik4;-SbPF%&1MXXC*K z{{ZE;eC!sYX^5L3F&syX#A(C)fe(eFISkfnTbLOwn-rb%v9}{=sbnV)=_+T6rfFGqip&Olf^X*+h^QNzs++ zsUhH#Q>+R1b;3vo^Z#kWNo*q6%udadA`ObceTs0Nf2L(&~%b@ zD+GjFLBG^nzw|dWw#C@~CjSwU(#%(YwFDp^pQ3tk4Mn$bBB7iTE!f)1B{ABa*+Ru) zALtkYCrp-z!(q!?SJ#<6uVCD1@`1+owfdYPZ-juqT9_(d2K> z{N{ghL8o>L+HrJ0T*wl5fM-+G;N-Qnb?|x#8(Dc>*$Z#g3vQ;ANxQaqRz2MCy{~)~ z)|b_KGbvL`NA1;G2I3QLgoSL>G}%Oj+OabYLtSYI*p1oM0D3#Ui$6 z*TZ`~@i|09b}S$NKk>B9SQsjrmKNd*4O`s?s*mG!Rwc-}_?sQ~n8&c^Sqaax&IlIi zZ6#?2&VPc4I?LHPD95g=VCcux`gb3wV6CdC_^>FSj`%j?gkd-uQjxhnO5{(+D*o2h z$~e>%7HF64j^-=MX%1a{ZgCg4#+S~GnCHYXPEB@u&ldQ`=uxN-K;9%pF41{3lug@$ zBSSYIM=yqx+1_~zxTr;$u<(LSvmC5j#Wd+j0yOej4*%;i*U0z?D{KCF$Nc-#?TK12 zCtW}zVeA_}Ol<4PV+m>EGYx6!TKPkC!LuXd2`7q3iHhVq<=;KfqepXY9HwCqO77(w ztIn0I0N>LUq>&V3P434=KxCzKZh=K}&-~u3SGn%u?{%^Dp%ugUW=sQ6>`$29n{cu$ z8Xvck)%Q1e64!y^_tp$Po($sW;#3bj2K7;lOkUgre>Tghd5B&;2NA`zQHd%;W!HWVzVsU;+MYZ zHnqjEh^?^kBj)pnY;&z(lyl~07`ui^`4!h`Yxb?w>w-Cx20edCO=hwy9djmvD%sWVyX61$w|{i$FMd&*g~WP$9wecvWj^S>=v zCKg}2RJh=D*bnaUd1UtrjCuoIYpFCWYrC-0@Q3TlT!*q29A~2D z0g>md0zY#a(tp$-D^@(+u#+G+!7#x9qqEUxuzn!r-F)gpl0p=9WD}rVQW$ZUqfxec zVA7~)d#It@fdKJ8uP2eQA)%C;sxhM+nsTlPR=}$`D!T!Lv3CXGDn$z7_yr2Dqds-D z>|H2vETd_aHZ-NMGfe;Zl44P0)LZQ22@U1fYtczXxvDw*s~vKnZD?O@4@1Wx@@Z;G zk|N(~>A_~RNNEF1zYvxBw1#_rsd$@}_PpU^crJavbR0^oS(+XVZz_?=z6Rr|p1g?Y zQ}eggc-P*Hv3NeidGUPm)yCgrZv=PRlnBX+Q7n^2ss2qsF`49#K8-A_`-2RA`SEQS z!nemcRZ^POWXUg?DN_a=v^F%0d5E#GsRfBDn+O|lfI@$(P}eZMF$*f*tT0<8Y<8(g zQvb?$wI$TVT2J|~L>BFa*-(HRLhs~}FJArfyf9nSaEZ?e6__}qGUkbS7&pn0kk%Uz zS1LDEo^Dg+Q-ez;8`>M`nBKnn`@Q(HG;S9fyw|)uGwd6q2kvH&Ul~!8thbw25xVCu zGIi2nm8!b;H7Culw$Ok^HKP-wOk%2{DY zrb_)8fwpOpug>lk^ga5sB@e!=)FEq}P#l$t{SKVfk=%=As~IMMrDQ%$<2{NrXioS6 zjsEkXBcjHFqH~5ZZ#W~}SLxM}#2M}UmBfnOpo}xNF%6qUWf;2=|8V`K|4Lb;Ei+G1 zeCebkc>IrkI;=V;)#smOY<>!S(+!*%XVbFum}eDD#D&(fMQBnaQ!f^>DFy;I+O*s? z@+u<$dsDa2_#LU z{qy5c{l|nMiiJ=ZY-jqgXoJEbH6wPiM7C!JDYZtf8>d_;)#tDE%Wt(rH#LKl3tj&- z#48J}(`^)L6$D7t$aDS$XeNjBGk7%Dl)uT0>nM=poNHl7tu{4PAS;)wl0LnrvrhlT zsr|c7sQW!-z|1@7Z#?yl`()}3ZaJDj$r;GI5v!ozObBx_oG|Px)T6HxXt&S~vLx>O z6*u1;KKA0HGVvp=3_6~%!bq4x!w_OvVogh^5h_11Mo~ALs5mCL?5K}uKP1CT^_mWd zP>n8oUhG+rr#2>Qlke*IL1W@v+s^TMAjE2-teBxi{?t;F`C2zlO!lbUqL9q@Sqr2@ z-hdeTmsVfS89pJx;@@X7Ff2gy8d|98GIoayOZ!jMTvFr#8y%TU$p!6dPOUw^3BKf; zNRVp&3i<&Yw?0E;W#NcdGkRuw!CnqBK1M6jy4CJ}9Hhrryj*rx5-J@|2#p$CYvJl~4#@6J#)A9>%21M8jw2(!mP{<`B z>|DLI;D_>!&*N;J3lB@xSbEctr@8*)#v-Ye;->qHf|dm@SxZocRz97*;CD1HG0#O! zq`&B|jUP)dI9SxPjPIy3mD2C}BTUJGzS|xSM5BzorObpy{XB5-`h>1C>3ZRM zq;6I&0IGYFK_7bU$!9*U4Jg0VqCyr*8 zev)G4YN%31p%e@bWBNK;Q@S&)dO(CGe{(Z!54mO3Gz-9DA&=YtS>q@)zz&Vo3}oik za4OM07mgHN0kw3ks5_A z5KzxPkfE|DRX6u-j1ULvnTvb+8e^ZIJu1ZL<_*AUf*Xr5lciMmG&{)GmAuIzD zMcuE9i}a?%wwH5#}tG22`{LcP7T0g@cPHh%BU ze4!X~%TrBBO81OEuz+l>gzIn6uXb2=`tsHouH#tjt7^+nAOGayB93fpu{;E^$T%Ti z<2I)Q<&RAi3vXyxhT5FqqfFEhXrFej+*E#L-zgQ|fqLIo^=1IkWhTA%f4*XT>8uLP zL}D9e8Rr%JDK_7{GFTA`hp8y!A8lUxjh;m_L9Wvd!yTK_F)hZ*KvxbPlV(3Hx+i={ zwsrdf?x#bBe~wrx;U$VU@0{qLP(I;{DBiQ@Z{j7_g1&Uzgk#Sj#cSmLITA1a3$|Pe z#QK^%*Ft8gfJzp&YSOqvK^u_)6>GrGC?lqR5KN@v(+L>eJ14XAwNfzVGqc?fFqJavR}8I|mnUIR5Iu$?&RHeq%jR59Sf4FD3jUKeL;bMO=ckRpSTX3tb3xgf1L zw@wObtjkE@3CEJ~#4<^}D=5kqbaC)yKlEcgoDH`$p02Qy|X|75}SU1q98wx8hh3;a?U1A zSwfS5i!L(GOCy5ucZSHX<>>bEq%hl}lg?3deYRPI=Fb7qbyG#o9Vcxd)P&wUdl9~1 zc$r1ZS3m3_B~&Rc{@py{u!)F5cyGihyb|%yr=OcUmfLf(`17Nf%8^G$m}!ijXJu{$ z;s`9XR_ap3!;8lp=c#wrz(1Y9U)#Sr8iL^i7%v0LGFBcyS*fe7nvqQ?mMf^Bx<~W%VAh{G!0y))^_wVyJ8!g1T|i5q708$TSD7uN_c1|HJvM|h|6FT$+_6#lnbcl*n zo%^b*%F>B4Vak`Z>=Ck zRYj0Sr)gv(nLiV)`5xmcW=0VIOEv20sNn+UEtj>{#2ay+8GELz6G`wG1O-zkDO!$o zHB0{p15=c9^cnJ|DE7Y*y^Ak@hn zJ5lfq33a$7Fu#0B4(AphxNilM+vEe*MII^A6<-Np z&O{RZO3-PCFQ4Mr4^M!m_`W3~FwAr8mFXv6(liwOp-zm$3D?hQkV}D_j%6NMDPCswCf)pdzkB)Ud5 zRzjkpsM<7{@S!?;eyb9+@LGwM+cw zJJN1-QL><_JD6l2C3#OkWkiO)qrk3y4d1Vyu&;gY)g@;aXMbX)P;vh`bJg#I*8gucc_8^@*?L- z&xrS&qPcw%m6KRjCXk~p{moYO#anbLjCUYZMfba*&@9e=Gg$caCM%1nY`r89>{{MJ}~HyeUwhe=qC z^`fF~E9^IM?~LT<4)&XF#w)`y^F`*r7$ZlCER(3aDjvQZn!FQTt>!<h1FT%|Mbo-p{rk~uYg18>@^(G zl>gl$5~e0V`_uK>Z@%)!J?{(W{bE}#w(vlpt;Pe7$N&V3mC&MRLnpv6l-WEq6|IDD zMnK8!M?z{U#*ES)gbc_{;d;7~o~#WkHTp~yeWyIHhdwb7K0|uxv@ZrU>IHmcOV-B&o;B zhgL0V!4Y*E`w?Koa4;V%h!i@ECoi<7qGCW)q9$dWNad0|DbfWK=UMT9BVUH&Xi8TBbo=UldI!ag8npwOk4qRB!*81s#K<>;ylApOg`Kt$2iw1``Qejc52 zO<5a!n)ljYZ6h_Z{+jE5md4-T+?F~_=Mc-vWBU*Qq>+g$O}*zEc6%d6KMYZZXD+56!A+@hD0!1{$0vg{IUkdC%62agDF8{zUDR0*LHK z_S_K!k#n>KCw3X0&DV4_uglZZl+{4|^NhOav+8C#MN_!6A`xA+edK(tfhUrIM$TLf zSm~+H0LjZ)`8_-!(mwMc)he|!GS8P@Iol%_&PPiQ-pb_}H|fA5CwVD6^@K|uX<)K4O%){JmV;GXs5h%nWidwHqdR%^ny7+l#$s9Yr@3 zcA4)n5q)a1c9Igt%hkHDA{6g_L>{EREbk>);Yx$$ks%!oLya%A%71`M+)hlHOE`%^ zn<%@3V&82`-~`Z&KKvCY%P{+lLy1j+B!NSeT8f(ZT(pfSHk6b*vc##m{3xSdj*?#* z+rtG~S40-m%>udW2u45WhBY)uE-?)sDx))&!`z3$4gMZG11kzfOG0Z`{@QX((HX{g zfYLvUuefq6T+JRLv=%*jr_sW@7{;qj*&Vk!G*OgIwX!ummIx(i_T${a=9K90ghils zt480A!I$yG?Hb~$(jsyZ)0kf^N%Tr#@`A)g!we8>Ac#9Z)JM`wEZp~~EY_r?JP?oF z9baMSSAUmvSy;~7u3V6G?SK*Z)DW)I;ZF^5o9tbs;>1DF-)giJMAPOYg<6z*5&V~a zcoOXt8!Nj3O5w_a10Ctgsa|l_U9wVQ6TD~qJ_`FtX!Vc*eV8~(1M&e8*!#M22!Sn5T3=l7AildmrGBG*DNS1>1o z1d2xC>#=a5Q+~eK4{0i=<#xDPs>wXCTzXlW zMhe)YVWj*WCQ~#No6;{=9l>1)62Zi`{%2?r1W`InEo6#`^%A1B3I%y!MGi?*P!?x~ zV@FaHTuodbH<7~CR2+AK^0{VPq&Z>Lr$&drm;muZRae^;t|GY#m0l~VqXYg#7)CUB z@5W+IDgHGVdv4OGjkZy|fbF`9-*YqvC{iwxf?HjgJ1I-50$J8Vyi-91Nx0j$5lr$q zDZog0(z9u%I%B>+efGqUVk}$RZ`@zPeEkv=%19VsLONiDzJN$JZ z-7~7L-7|cA%7-P?38mi(6fs9^1djoW_mJTam1gR@^8J#i#8J$XT-P%79hx~dA<^AK z^H`29SG_*VKmqujfJj6LT;w|;`%{k~Yd0P|rwt_}Hn-9gy;@aIKR`o3+oJ}FRp_S{y-FREA93}Oi=}1=gY95r8F*D7$ z4=#bpt+K{gmp3%h@Itrvw9p6D+%dy5e#fILqV7hhHat35<4=2FUcK>NOERo0V6o$A1oNqpXZ}aE`u$Aok2H63VabKy{qT;_goHNXGVN{{8 z#DFwwM3Y^)r2fhW53*~x{JE@jZr^4hGq%P0czFsF4d7b2=ef$Q=MS#cEHExaZVT1{ z;~b)mF6Rx#pvcQ}7FX<)+pgDTP1+Qw&fCpgJnO-FTL=gF(1daD0d1Z~Gk#04vbLH^ zz-_hpE;yx12M?YPQz_0+Q53)fuQD6EzL7mMC?B2nrCYAaD#gS^z&n6YPBR94h?F2$ zNFoB2zHyA4&8O}bw}mF_D8FY;{p z4?a3hKOX;krgDl=qB*pCDWZDl*s#LmG<0qmYJ9LJUr>k^r=*E3MrA4yG%bNY{J89( zREs<``R!UOaguZsz^#yg3Rf-xa*Pb+A=o#a1|e}Vo$A9i%=$6in@fZw$q%G*{SUi- ziIT43lH@NdgO|V_Jt)~5)ThS2T?wcu6z_qU^68lK-2tV@I!UGkV`__gZd_g|bPA5? zX4JEIY!|!7GA>mag2_b*01e13Gwz!fjNygd&DL-@%z~jzXb7zR5gi#s5vquBAR~nA z0v04DL;9y}vK|I9) z_NtYfB|%`--8kce&w_WZYA>BOb$SEVd`fgmXx%PD1VCeMZq^l`ABT-Nv1S*N^Q@Dl z#zS%fICPOlTN{+gA~rkIp=<+NTtzk5%Sn&Q5#2zjeYl$Xo^*lgc1mWwG%7w=8Lz2ExCeS4I z4$9LU2vh+>1V_FJ`7ors;f8dcr4@uO3Iwl6DV+MUiQm6J6G-LyAEp`Cw?sI!-So7s?Avv4?ElGK3Cf~OiZ&9vuK z14!4qZ{GYIKf$`zo4PubByz8#IdWYY5X#kl@b7aD=PziKoe3=xSThGFYq8NY=Q&V- z1ekS7x$?MLJbh{q-6t~-r`|~ihY57I>jwbTE{fZkLD1Pp$;Piy%q<4e5DXOf1CfDP zC4X@q0MsZWVtYSsCuv}lCe1^L2U5`^>JEs8%l&R>#%AYZ$^3!bJAe&mzM~O(83cUw zBs{P|1Y$j;x)Lt^yoB-8H3u#Mr-+F%0SCj7jBY#v!jg5MUCRCb^7X1!A`E%cB$Gqy zDB@%kNYE~f3SG%1A<2!HD;r*S=|Tir89+?MSZ{=I@zGHB1easLuE=enJ4U6%&Pq(P ze=Wrt0Z|5>2RMYQ(tS#Gk+)GVaE8SL=912@3Fh&mSOX4O6Fm+nT>2j_P(G+8K(OA? zHG-)ZpGGVZ#Xn`r#yF)k?EQ5UhIokOOUc-o5YBxc|7|Rp2e05ds{^h{3Vt+O31v|344aIM zGm4inhn{nzaAmX&C9zj4frwDC0JnmrnAifY5%hH+ov4uoAWE<#NgB6_HhrX4^k#E-E#u$;&Q=9*~*koIscXwCwSM5;{j z&xWp|x)xT^*Ag-FBP-Q9so&RPT(D}sy9a^zy0DV`h`Q7hSI&+~rwa^Vv1JX@gsurR zwb&VOiTfZ7(i>DIK|o6=8w4!vrQ<2XmbJk042-8a1Aw?r=q7rqtO0?Z^)cWspr;`q zs%Vdcb&44xJo_`1723Rz__jz52hES+I)05n;ZrjqgM6zQxp?S318*1_$vk1(kZY( z^7_#DvKV$YC)APM#tvB zF)VtZ8Kx00qeET}4>_*WS$9B!3W=%#=p;|qq9rw2IF(H3PjrJ0miL_ky_=fYH<(%b zPW6H9_2)e1{HP3nKu|_SuU`5AQQyORjm6;-oj(!v^_d}k0G}*qWa?Odt9U2dGr^5P zCc&I#Wnh78c5P@H3=BIL0W2w*_VlWz#S+dyq66wXPy{&zP(Y#kl?*c&naqn0V-Im! zVct3kcqbKgw$(-mGhkw1ka_ehXtI49?zk*dqCU_~lB!Hjb1~u-X|2nJm0drBYD@m$bLwBhf|TkuZ^f zm}gFuIDo^P&Sg+U zP})x7RcPA<(y(?M)(wM7$61TK8pLHLaFcoFLG9`+s~KhSvofMWBYj^Pyg__~Gz^ zVrbS#zm;grG_HblLAo8oP9-#NZWhufM^z{3$3WUXaXp!-{3nNL4!8}cV&;ca=%d3VU1nt3Zibk$*NxWDo#&_+*|0lf5wV?=jBDrG`mXh=@QcmV1oxO$u)7p->W4y2zy>e5D@(8NHwYQnOtxt2>|}8N^y*? zLAVaH#{wjP5`|*22MN^&kfV^vT3GoBfg)2d0D~#z%a$(LVn&qQ_*P!*r8zUCG6=Xh z2)Hc<Dp_VfW;%qc9N}3_UXK>S6uMG{LPNv$U0AX?USRQuh@!*>kjltVfT(mB(+Zwq zg5odCBCXx1G$Wy-UE5Uv#?9=l*mm8)yx2Nk-|I@sJRLm%^SpL|459|Q&g?!}8M|UQ zJv+MwV>MeE*c@%Y;7T?k z97s`Mem7DIS@~7AlTK4UNweiV>x~Sb{@XV(9;ls!iLN^^iEjxhs!PZ&-&GZW195r+ zndNf~o5y&{3~)cb5$&+}@B{56aFCAkWD348T0K@~OkjRv+rdrAe<)I%BI2)PbzK|s z@lCV-d|y$1{46^TE;86z<-=ScRwp{iz6%o(UH|^74(U`A^(JYLS^Px7UNYX#$!tEE z8eLVw#5=>3-R9@LVgOe(L?0SjGzC!3xZ+r{(+i8_xgl9G<)?l|Op~UxGr}(IbPX0a z1bc~Q-CsQ$w%6=9msPWkij)lLN`s%BjKG*x$&BJ8m-_)4ksZrbC#k7mq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c537f8382a42986cc5e0d5a06b1460df4f0a5e25 GIT binary patch literal 38232 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 S)0Gzotc-JM?Hi4}ZTDZPiC;_r literal 0 HcmV?d00001 diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e231183dce4c7b452afc9e7799586fd285e146f4 GIT binary patch literal 22660 zcmZsBb8u!&^yZs4wmESowrx9^*tTukn%K5&Yhv4(*qAukeD&L{+O67q>#5V{x##IV z{l`6h>vp@zi-`e10Npn{(tTN_YxCRmIVMn%D!3L|6nA35hpGpD)!9{ zef#*|AOyh!fQc)}D}8f^003Aa005ms>xd~NuB0La06>I)#{_(%EYB!BUtWox2>^hE z`}Xz!L*CzXKO-9h`)|(rTVDVG0AWyXSQL$1oe97DLHdqi_y!N<2n4sOy_wB7C-6PS z>$gpag7p+MGjRIWBJh02K>cqZnOS?7esdxKfFK_LU}yi!vWwQ-#K0H;kPrTjVg3di z2-xpH^KbH-Yy0*IzVQVPvfrVS zYieWQ{ynbJ^SADs2M~h(07BXt*q8tS%2?kqOW!$Cm?1=S+1oie0{|*F-`vZ0f57Xy z;#_-2lW(os#kVg0KirEDU$~hVe&?+2{p~~i2eTH%+HVW;4ZtLC!OVYloRu-^KRdOA z#p1qhq;IURzYA&z4S}R@s1G*qBrpj)V*H+W90)N0;J#j+A}jM-9BcHeljaJ;CZWY* zA0BA=y&k`bikBmz(zvjl#zZfM0XgNTDFX*3`2E}*s`jJlw1If96@D605R9|_vG zS&$Cj6Au`o6o)ET0%_FoG1XV#N^O&LG){ldbj>_7>UV^viY#ezHft8i%G$eP)w(MHlIZGb>OBVKBV_g#d2Z4ZfjiY@6`*P!L@TlmLz%OI&5gy4-HJ>-)t22%Fd#k)&OLVDMsL{u z3F+<^`fj#|YixitJqW%H-!Iw*Hpl=}(?_crz=|GZwd_D(-zD4B+}zvfYFuOk582X+ zV8T$LiFC)qQ{k>~RlY1+S8V22!LV~hvI}a}SY!wbMS#b{;bL(_xf&mKb6k~R4t0)c=88?Djji4{N` z4d82QUS>g#rR$As|4(!GJ)pT>$V}06?hqt)ci&$S9~J3=jao zzkxxRety?(C_|tUApj)zzh__);4R;V5CHn$9QE~0{q?aS#0bax#(;;6fiE<0^!`oQ zLBM!Y2;*C(MaFkC7GpTmDt)dI=cvQyo?H9op|AXKD*T7fL7uILb z$JxH@}Epi&2Fyp zIgEC<1*8)xbb9TcOBv1QD>kcb9_J}G+%4B@-EIWJic*$GACV#8YxI8_u((Va(U=*E zQiF6-l?Lk!)r=hR!?U&C2+PY|UiU~=>^9rI?w934gT!-r{2rbke}w+oc*4^3%<$@b zC6~F#==a7XY=w@)SsO`2h-gE{}l-5$Z>b zE9tk=kn`~cF&6jo1u`J7A3snuKQ$*wZmz&^CqxXoi>G*+!zxpXQH8>?_fsI`JdOEYRRl6HI%1ESG z9@HU*OZm=`FnMY8*C}7bkB+^+^@;t2wqvUMloqJXNh0Ic?A*VlwWnQ^t5Bco+%`Ol-MC0$)=$w6?23s6$mC$VY-D0 z;h7M>*l-@p1`9d}sIG8lI*OYi^otymNwn*AZH_t}xNaICC96;`YuxfP!d}x7Q(vj= zGbB%(T?a($mz`s>Z}^T2J#m{&1cdC>LbmG=jtja1wwf`UP1Is87f>wl^V6kNfq53j zkArR1Rjfb_*7=9xi1E&FqVq~rJeTEVDnGQZr3iZ5vEqoFs|IatR5y#QmYcm(SG_Gw z=Cjc15%$>MVYdwP2eZM`cXkM0E$l9x>Q1Q&$%2Sw`o91W6jqQZY0GPJgw-n-`x6BI z4%qvg6S7Ocd~z6BeCTK1I^vR0uf2G-I3{RUbTma$T!J>!c;B@mWn4ZAyNZ*~4#Qpk z8f!I&G8PR)6`WH`dc?N49$=EHsBTBiTfTUs+!?Rf3!6_Y^TN3XQ_6aThpi}6N+CA? zF1$brYeh4`xBn9as~I}fhTwu|X*G13?}_yTmMAp8sT-+If>H;4r|FN|Eq( z1L{kL`qmEw%_jjwbOPB~36&|v4#q!NF($Gvnf`Pmf9$ZTHLZKY-pZ4jB30awlYE@^ z@v~f8^-OwGoF>LPzSi?vW3+Fbejc@o2KXHdT%=S5dYUmI8G&%Z;tZ}193l+5z|o)I z_{qq9^}@qO9co;fXH6*))FebxwNIps>ex0+gyJ`IR=Ccuikn+oxEsde;m3xgVByAB z``!3Od-dsP#{)Q69I?p?*mTNDJ=;1)Ev8l^}PAUs+-lwl$ zUX$!mrrTtu+msiohytaMaTg01w1gmD&S;rYD`@2EksjyF#Jur~F+~tVvtIi|Pf|8-G3%;lO1qZ^?DVJMQ-{>8%qD9L7od)^pCO+Cbxa zUm%y5@7gdw_Tu=SY7A9^C{30Ix&Yu*_)AelLRmyKMc-dPnKoVh2Fmt%K-7lZBz`jb z4DM9nM$6DZ&zg^)=Z0i5)jv`3S|DOhzklR z2m9dHywCE_g2RDU?~8B;jVX1O&%ZZ;Z=agK9O}<5OJ{f*cgJ!zM_a6SmTP;?@}v6W z!sM~pk#p7mb)6HW@{VtG;oT2dd|gylrq+5pG~dqWnB~4KP!^y|GFUJ?4!?CVV~Yx63`Mc*A$;2-BlbC+fbrzi=_*lUHuu^I3+Dz^owT5w zr+%`zmmCNiYAMMGEXqh(0@E2i>Dq+ZPOELuk3boP=)QYQSPZ<7=+L;k*qYI+^*IT_tUr){! z#JU-j+$WQiVTq@6ify6Gu>;*nh_e0E09)1$V$<;2fGiKew4WkH0mNc??dgHwr-VU! zr1MdgicuGnLwVxW_|zxzmAO>|8z;}`&cxddLiW5uVf(M*H@e9)q7P=?h#is66tue# z!HjfdaCSWL)u;ztV%_>h2&cGps=BF@YbyTYqN8zBnW?i2&P%L0pDfil$I-?{)VHF) zL`nwM$sqQTwb}ymRm9uW?h7{VH>aiES$opcO^6Yd}u*{fWA!3404*!^q?x4So4i{fta|ye8;winh8S5weaR+NxM=vwv2JQhRlFm*vYbtQRLG8zrzrfj{Wlh z5c$2cf8tLo3%v_p(;STZ)3AlN+FWOIE?#oge)i5Eyvc*Ty3e2N`(??HiO!7h=hHs> z7GLh8)>#4YR%~?X?*g{hZ?AB^@XNfY?y4ksklPyya(RW(3E@%b>EXc!(W@!@E!ml5 zsB|%rkqx42xT-&_>G5{Y_A+6sT6f^j4?y6lm$ki#)g=%vdnHn_owL{HfZAeD2Mx^w zqcPaeQLONVQGt!h*--CN!7g#)qyYk1K~Q5gkiMr3_pAU^b*`V$0Jt{jU0XeKZv7!| zvdm$$VhIZTQR+MuN0Cxck6)al{wf%575k0M>{PkNJ`s-(Odl2o*KXt&elc{t_YwKv zhe9`XZXFEQ_w2O_T;}2_y|&!bk~D-~>Mbm6Gs#ts0X8w4oOI+>gvjq1c^(2` z7891C=<);1w}hK+mNNkdJ)djlT~B8})OaN#?ig_x}@KWeSM)qpO^AQ;Fp2h=hxn4qkfO!YJ(Ir8t>tXZNPm>JB* z%0;7&myJ*lZ1j6lI^6GDnW^j`y^}Bo-4mj_2zUf!MWa>HpnzZosbDIAQ|KLrYp1gy zisc|!;GyixC{jR-j#- zZGJson6dGxwq7ocrtH$)tIl{DPF*z5rx$i!@!4<0^Uv@)-(DK6sBQb+^pNXz=(>F+ zCL>0#t&-QNw4Hz6k`T~c{TmyDZba6bz{v|bg}}VCw4wx@dDD_=5IeHg3HLQH5O)RA zvYBaHI~rE8PiLlB-nSXhGD@VKcdCDkYp=Pu6y`H)jV3q6UEH!ZQ@A2BY9dFQ`c5 zjpOEz8Sm(h(fK`paiInDe56AP5X0gDfgbEHRQlzrvjcP+SH(m3y6@eyd!bc zzj-EO`xf;gR7X`|RmkW}Z1VjvhUG1{iw3@^BZLaPg~wtyUEdk@-F|3Z#Nfg8_w*ms zr85+{9K)I2&YShTt+Lo|*RvLG9j77T>TYsMb}!+J06q_7P2@VxI>D33`h40HMF>@6 zH4qMOc6$m@=2q_1iHc32-e1$}oj2;Gui98I@jASaC zWSyZa*B^V~kYvzR88I8Z*y?R{Xx*&WquAN5wr!ZC#3t{{_mhdY2@&%k*6-sXnc&38 z`46N!sTk%>-r$O#_hr@8rrX%S*MTCDaV2C{e65;j1 zA@7sgXU@A!87`(+mHy%tt4v!o$^IXnG(~U5qDbNdF!+|M(vd6i#9aB?ml5NuQ8RO~ z^YvE6MG(D=&f6!aO_dc<@QG3n9NSWqzMu{W2P_@V?c4bV1FTN zYilWMN6U;(ok*bAST-?}$pu<9!rVbiXFJ67kc0ZixD$>Y3Vg*>;Nw0Vg8%|x>zZ7vYWh(?fLf3Wdi@#(*n^@P_UsXwa{GkQ35A)nq%jZIe-~qL}`tv=0RN-s1UF!2P%dr2D`OfF7n9-rb;EL=veIOPSV+RFY_i88?R^4=L}4 ze(!k1NoaIen~AC|i6#ZXrU<*apPu+=sc=z%DHF3fi=C%f)RBQ-BNJJ^7Eu;53A}f` ztU7Kn`@EJ8#J&_91>OoROf;SZsy98CFhZgN#==`%J+W_Ob)H8z4o6wTU_-15VW+^l z6^IUc6n0xj|MjAJJ3jc(`@nlKQlGgzj|mNr;kj@N!}H1PJ=&k&ocy5j z3jPt_bI@N~(IhpV6-F5#lK1Be0zOEyx5( zpqAt*bQw%OF1&M%#aoMIRCu>jQ+}mU0cx*g&Y7>~h_Qh_eq=zZz!Q4+so&bIZfZ(o zIS*3SY=DfBOGyDQ;GHLJgy@I(-zRL2tD0A}llS1}*tgPwroq@;*om-b^io>RSu!c| zx-LXIQ-t(-u*#veDp!o(ZM^DxMF#vBy#lKqeLJf)?eq>=Qrf{-BpVN7PouS4qK`hZ?VRe^^;#P+$y)|DG*KV0NS0iJMJnE^JIeqvNdRxEwkdqs%3l0duP2V8`dyb{bBS; zm7++>sk6GA2al@5gCjZcBSRIV@|5#+c-xaFwFtbB&F^*jc41WXVCM@D%rgl3JV(1T zV?oNzL9@_6P52PDl8hmapm3Z>VG|SD>jWv`=Akl#bfC`BX`SB(GVVP>m$HrYLvKEL zxC!Hlq;~*38PY5OQcRy?DAn`G6_W&cpW-JBO~;~gL(4@S-9K~GXtqEEP^$<|evwj9 zpiDPWi@)ihRe(#{CwwiJEJ3MRujOj@adF)E$u7d_EVtR|4mm_={M`9+mBt%VUBJsH zn6oayJExDfu zTI+3&&t6N9UY)fXPpQWz?Y(%@+-+v3CDT!RDh)nId+UkdS=l6D_;9`Hxg5! z%L&tf4>_ZiK5b0N@fiM71peJlR5fmkgwdC4^_P=QF%>Ok>}T>PoFDy4uIJ;h(tQ5N zM(v!ugH&N%ZT-{U$_@uHt^vbt+_NT!_~1a0VT&;lHUuts+7@Ev;V5IxJ8;gO<9X|9 z7ZJX#O4?ErlXY&<{Y^>Bm2cbuLZ=wc|79O*TCQ=3iDZ~YXTA#7$gqlTslZ^jd(wEx z&dkY*@WS^rX6vDV8FSRRAor@o=||56T2g%2UkK~#!eVzz99wcKWQtAp{1NuCrq0|8Z>z-+@eHdTm>YBTDI>`SYDgc#ca)?TxV52)KXBAR+X-wtE~cUqa@kg1Gk+o!(XG8N2gk zK8wUT0}bKh2_hy6`)nSKO~Dk6eFvw9e#JH31~@z)$U2kq3V08sj6@t(5>DLjmWaKE z))kl2@9x5IAj!WL*iWzgNsNn5y%|&Ab9fyg{s%X7fC-*?5z0EwRfGv0m9m5yOQCXW zXgz{NcDjeD9i;yG1`e4!4%(1)47o(KdUffMcbWd%;&M2uy%vqr3vUwChqL1J$DWM? z$3+xN6NP?VKu?n)3Ln2kl)80@vFpDQ!h&e1;j|hQ-V_t2Mc`piX}iMJzBm-7dVghQevE3B|CX9ca(Z|ELQ$zHMQSa zK&kG}e}zi;>YwCayQoIGei0e1e0pwo?OrWgE*n?X?*5{5It;CjzHeDRwP1M6=j?Gx zzr9Kj3BXq`AwPJOT>VoMqFpPUJvA)#5+u-ft&Y+PVDPG zu>Bb~i!}n%;;|mYua7Orq}*%Mhsm0SQ`7h29#`p)qjgOOj&6zGu-M8^wEaK{q*pOGBOPnF0TFtcJBDz2%pR81 zykQwu>O9E1bIlo14l!!&{JHwqj$oYG3oORbEU5gY`sYbE!o{$d_2{LNPNgBr>1-?C zMMqEk8@+#+I^f(e$YsrAHW(cR<&LFWW|)Y$?JISC{VemI+!>tx`@m_cP;h`y8}8v`nRI7| z5mv!2bx(TY9=mVcA(Uy2k4#0!!!;9csV*x=a}encb@2EmokQhF{L!PmkAv||Ci5Rb zcVf22g57f^q;3hpoS*jdSw8k93}|<#%;(MFtnQ*_=iTP17kfA7WB(qk+57QmI%1>` z`LJinKaV?fons=6^kyrB?k=OPXP4W54PCZ_8y>DZTQ?a8TopK+c8)5woguahW?2246s9!*3G7<#u4WGvpmG_WKS?cBo#n1cXEi~qV;Om zI3U|Vg)L)c2_!2h5zlAe06(vyS}C(JL6*ZSi-*zp;3ywd4+Iyzk;JheiLNhuTIq-- zH^^MXyb0h3Ui!`vok!D=T#<*6Zk=BEn8QK7iwk`AM)T!-u}$Z+psL1`g?d}|5s*5u89-wVJPf|zDiUsjHW|czRY@KAlOZw-@BzNaO zs`if-)0;)))v35qI6 zz(g~cD9{TMnw7mr37uge3d6X5-NqH0hvf*RQAtNs3q(7e6E4mtC}m%|^t8*P)Adxs z^~u4VZ3?D_@NUbw;KJOyQNM$Xz@1_jqElIvJhGh*X94xuj%cOf47}16>DAFbO?0B#ZQ;@DgBXpfxl0h0d4_tlgntC(W2s-0$Eh}(I zDb`;M@0srB^;J9&vk!#!TED6ZQ(aR`V&f-GkzE);WF10=l>cqBTb+k?yqVf*X|=Kl zt~kiUj|4fdiJKAlBxLC}o%BWZ+g!Zm?jYtMy)CD}^K&`BPxyh)E&aooy%G>sUPmQ% zMJU&A|9z5qMNQ|-e!=6S#~B}Vuw$v$PVBa{jR&Xnl~7JDU$5ix02;f#OBI`HSvvyM zmAN8uB&bPgN32bG11OStOycK{H4r(_e0-k0&U}W)sP*>E#n4~+o|T*B`n;BN?HBXU z-pA?Rk=x@iopL|C>hX6te{K#VrV&7T`jQ=o{g{GzaUeF=Ms{+OF4OnOF+Tz=%Smng zS(L#nbg=pYblZCdX+IyS-%TF&r~aL`>pa>vm7kS;eV<5y-KPO1u3-t|SfnJt%@))y?S!gEp(0)>w))iBCI^N&OD2Pq z)S?uqO^LBngPbW2v^iL*n9J}>g2n0q<*cIvQ+u~YV+;40k;w^I+>B$uGk&ESI?&a%4qQ;Y1jNZq( zV^({6%}PoO9#trq*aHQwquUp$)*Bt|EUNGl;iohy#3oQbU=JPD@!Lc=^2lNOh`8A{*=T7JC3c~v+9L)7Rz644WToV5n9sb zb?_;!VCiumuign+8Kjz`+%B82r`Q4eg#$xb?G89;AU{hPJ^O$(%kosZ_(20ku;+u) z=4<@1n?E{}(5gt0DgV40k(+$97f`hDNRq!9auMLMQTNVXXjeyrQj)obZwhUX^2e`L(B{Gw zvW?p{htf1yNr<0jO??QTXuHiET@_uY`H?o^~!E#(2m$q*L^5Kl5dpv;6GdxV)Hy_Js zpn0fg%Cs@?cLgP7PUhV%iSwNFYK+pS4CY?*=*h-Iwb9SawiAgi>SvW38a^@Ur5ETE z2J9oZh9u`wa1lBjSYl}kMp_zGD;fy$a+H>E6^cjq3)hs0sJx_VLbvEh2F{yH!p>>s z+hLH5xwn}KhzDwlEhjBE{ih7XtA{U*oA?r0&FKjbCC7Mr8vNUDTFvPVf&ZHFQB zT?wa#7buc7vu{=)6k{-1%1}35OfBv`>#kpX$;&Xq_Q9x~ERGfruKC=*2Cxb6U-$1! z4u%qpNy~QvxmDGwiAlr{vZ}q*#>h{GVfhNLfk^hrnq!+OJ!nFvWR!*+LV{^z+sIT548+L@kWth6?0;YH z(t`RZ3~}a(sBuKWhwNYeB-}S*@ZIcgjFwKexlvKx>GbuW-bMOko^l(B#jB_+J!~HF z3T%xK}%igi$r{4ju z&HTnsFc_)wS*=<<434@y_06fl1VcY<$=r99%D5vQ=CC=(bMaM)SPi=f0O&M@4hRFZE495ocZXjRrPP>+?*~$z4xgh3sm(hL6$gl^#|O5Mi;cDI>KHov z2)nekq0#e=pD<{4j3@$h(twpEwjE$=2h~{q&Eyk=17<`ze%5QC3-@n3eB7Ihm;sQTfVAq;D3OzbqW0 zSIvd>XZOuRdyEx+fi;F-N$Ehof}gwf)GS|BPGqf&n+kR{hQVj$y@`!X5JNq^j?f%j zXgWU1m=3yKb`yEmpQr{K`POo&zbSUR#rtxg9f=jayrYW8r=ZNhIqHBF2%8bzoY;ph zYO0PPX z$QV|~=7#H^cur~*pD1r=9ndW*SSfZn{2nT!n~vm6FWVba_>+Zv>D0;1y@e5kti>%| zw&MLBp*Q!DW1evuW$EJ=4F{RN>BNb$Kx{!sgj{5Cu+QzWcVXQe_U=5wt<13FzaHJ- z;JS7>EUc}X4>8(*&JE`k`8s%KdsS@UP@L6y@kXk$AfryM4M*xAaxxmuLl?6bndUghRksjH-OG+ROnyaRE{$S4;DBL#GtDVoj&MD^B%WOh4yW9%f;BAf5UG0tY zy~#RRYc+YAuHxrf_kP-IC+M8ITOfJI?zpdJH{a?syS+*BD>(l8R$Z*%8#yj(*~gd9 zXA1Z+d8#LyG=d+(Mnf;?=h>kW>-o#7R*_b%2RFD#{1VWS=zmHDim(hQUIwDL9pd9kGp=k`W$MlNMr1rQkX8(ZI3&?+k1k5 zS*(~ADIoQVhQN?jAwuEd#-17Vm);?1mOh#rvG@k&{;6b^Ci4#y1R;e|{0|OuWv0ws&pD z6}uiHDf5x6P8XMEJs3>Y7&}EPo2~)CNyDd)3zQ#Ag}%tRM#01`BCd(a#nAr_2ex7;x4E#gzlD) z>nQ}yl1;bo3p;6wb|uuqb$gYyElPI8==^9%JM8I?UdqO{(+oJ@hOSTcX>ie(SHuEE z*U95o=N^VcZE)ZEP1t)S%?#EsB&n`dCt=ZC!jJ@4>(BlWSj6PoN^N)h*U5g9h0+u? z8O#-W9%p;SzZri*MgK08s4B~4Ln!rU1P(RoVo6iIy0Nwt2bl#|!Mwuc@4~63Vy$5g zQY}lOS4A?ZhoKJ_{mzgfiyAjns!rL?9-mQuOHkQW8)~3JK}B$pPiyz9!9xt=qO`Y& zUgrm)p)lX#ClWVe*FfKVlvQc(tfFwUuH6^S#Mjkp_9fsGdR6gbbe{BopVvL*94w*f zstb_6FD2V`rB)=jO?{If9Opx5|Oi zz{s(i8DeLVi$DEa{1$hy&0_Sid9OE}<+IY(khuTG^+ct~X}RWlJJHaojpxSKRC2#L zpKV2sNOh^3af+Rj%-^|`PH+GF1tOnW?{YWYP2kL98)T%BS#Mi&IAdCXl^VaRYvK3r z*7a*x8RXvU`rgvU<6G?%w*dDlG{XWc7C!H;60wykK2wIMIO2nAd!h2nsnBMqp~07* zK})tFmu7C~+UcwFxZ%uvA%7}E=XvE9X`|R>UbY`D)WQpu-8IHoE*c31?AI~-mymgO?xjU{r*J_Ut~OVlUBto9>hio;pK{ZL2<95 z`~m#Bf=X?LHV7jvxKxT%pg(-hS$CPa+HN~NCB#$YwKyD;bc;bNz2NeG7%xS@Uw;9- zr*m6j$Y?;gTDw_smyGi9()A_2%C5?~%?yn{B&EA!Wv{(6GtNu;++@2e({oYgzlf`t zJwkH3$Z-uhtNIz==Ff}~2h*JHhB0kDhQwp>L{kAx=8h-?`z6%@+mT%P98&VmRRfyj z2*<+_LwTy4lrT6n<;7gk&{*U}q($`rNFGNh2X%4cRui#06F?_uUr*7%Ro(#IF9W|n z`ZGwjkgK4eA6VAu==;)a(P;S`&`?*<(eYp!IORestiqToCs?hI?MbNn#Cd1w;3oF{ zBY$j9S%QAd>`uLlhWKKav+RJ{^Uot#CJ8=*tPwNUf{O(f76>SC8D=X&Kt^;|ZtibU zxd2`1K<EvttqCCi}SP~&$N3SnNr;btH zcL9yd)f&4jp3i)8h2-ze=fSKR-bh$=jJ~hF&_5ZUpxkk}8QT`8CxwsQxL3LcHz%R4r^@oV`)=)-RT2%uMTKy(gtVEh6!t}9TAPL>F!B;nf95G_w z2`YuGy+$yG0NP~UiI%{esDPxDHTWnJbg2sO@ zYJtc(P-D;(2Qkk?!UPdQJ>dB@U}~@`i{@ZXN+dOmCP`{&rnzaeQsvMWHd;iz=Ce9q z1q5=>vst!l&@>VVyGu-`<4v~v=X_hRMuW#GqgF=CCJaAx=^Ez**C+%%pjgou+!Z0k z%D0(lFuz_gwc_+bYlUKFnK3!=a&1Jf6W>1=oP4C624Uzi@AQKC4nCo47uGqcW@1 zFF3sscsc1w`z9BRGy7f?+DaO3c?ld*gqY%!B6@oUTKn7L(CZ3JF;81smQI_;H}SM( zSfguBnX{d`>|tkSWNZh&kcpn~xU?ia%rI!V<^>H?K<}N3;O5A~OqsQYnEgi0uprA; z(Loh-g7?8Z3O1KCrX#WX`q5vSD6B*}RPX89JwUGXYz*cCmOY=kGSsP_qG!mdrK+ul zULmc>?olQ@Zu!`!M)kC*k%}Vy=T45adTBJ5`0;PIlvAs9Kje-6`)E)HdLn z)q1r^%1UC4Gv}5luzy6;5^5q(8H}q_L#%rgs>RB^LosM-UAQzxIP~ikNyH ztInDtxtV#)Mpd11gtYXha{}<|zyoYWaRQth0>ahFW6e3uin+|ZwZp0=;q>ddIT>q| zyvZR5smj5(w^bP|XWsxpZvVpd!334!+Eg&%-VO{Zpo6XrkYo1A!s!n&MV3=1oK!Oo z=r8bO-F6iVPY;||z<46Bu;NC;Ge`PsxkvW6Pm>OA%y~S4TL@mxx(inG4yWRErqDFgm3bd?TAh=vc>#>?oNO~h$X<#=u zSr2MGFj}w8bL3?`R?k{#1s~fQeQ@`wZL8&<78iQ^IWPZgWw&Rek6##Bl5+febOdX& zr`!v-Q8#5IucX}jSM`2c$ZW~O=(4)#$@IQO(th~8$3worgTc;#ke_mUTQe{@bMiti zB25dEv-K&o-D;LBEprDKIgx1#9*+Xc?3w3k2rN}86D><=sTJi|?BvuI2eZLoL@uDp z+?BXAyy`wS`2zYvsNAwTBv91gj4^Z2pmD9}P^NmtJa*aYH~x)3np6ScS1p%G0=ZjV zoIv57bHcjQUr1UiwpN{~{NodH@w0RKT@Ks@cblhDJ3PO0`oO<`R6K>a7K5iDzS>P! zjN)!G(o5`yY#f=+h8otpOh-Z)sS#DJOc(XQnoUEy@j%tfERdT|L=>b$P!~^V`Sx{m zW4E))~py z()PrLy~#oI5tU!iCBD{NaR>Zj@23?q*b46BDcd`hGkyavmQXy^C zv^V@`0a^=*ZA=EZ)vN;&O<;Zd2S&be~?-d)Yl93ZO<(fOUEdqf8FxeIfmcF^* zIC}~ZoP71p&ejWeMt|YKlkLrtuoys#%<2U*P%i3< zmINH^{K0A<2&W~1QBKCP#O}< zZ0+vHkM0s)nzJH`C=cO|Prjg2JGL_N?znTAGYTXj2Fn7^AD~eFz{&Fm0+D55 zbVP@fETc+At^IA8KY)=$VDkLyLtEqzqD_(c1K!i4>PC)hU)4q(L}+y&+M7aT1vx)a;P#X1vW5?EC; z;OZa_!>`~v>voQ-yA4s~8*v3h0o`U?W%*ZeZO&r+E?m87DarpETu*{7SRb(XJZ*#< zkni1x%S23G~zFm&5x+zjEUcujwCoK+nhfpZN+$wLDbA#9tw zy&xV^)cykp7_^pf4Jup)G^Z2j{j`*%)?kf{PfdRV=W(3MC+_>cs^w5v+NJLyErp`; zClNeDQ#B#U}X6?(nuAWH>_No+lyMTq189Okz_8v$unQwoQqrB*_a z_&u+o-k_F{)Z_~mT0wGfNQ{q7ERQqf2AWP%R$V^ea47Aff{GLIEn&rkGBd4!9pX7I z@bv-KHvlVHU9$*SHI&^lnHorD84C5dv}G3&PiCnBKVf&4ieqIrzso5*(80)xDvDXf zy~EDxs|`57ig5%?!WZkXYx+DXNolF9%!0K}Ab#(ct03JcL4fKjh~eR>O<+E@TJbE7 zrPqJ@JN*hPAALGrSNJyl?zXQ+j_S2-;?)6XH$A<(VH)nfcWY4^<|09!Uuc6cEKi1dNP0t)Y&E=K%oq#{Y)^tCoez58hnGsr}vbR&X z*TkSRfwE+o8%5DqFw5^KiD*wThTBteTRtMTdZcB~iZR@?k_eF^&TQ8<-Q!M9Y7-xm z<;ntc>tuD`X=c^OnXd9VyuZp-UHcwFqYinJcnBT39Tt9u0F@nRn@eumx57%#Z%7oi z7*TbYrHZ^Pt#eD*vxYL*$?-hQ4#9?>MYSL4S76_eP-+d^`CG70!YYkB>~+Tr&A>hE z0;k`Eo^q4SQ%mpxy+cJnaYyL3v8wMJfy1fq5IbRtNIFT9Qo$6P;}*cNk`!fXDyS~wBh*EK)4OILqx_t1B;>XAq2 zKe}}<>QWdeB0p$9aDQ-m(=l{Hh zSF)7L^I7@4>uSq=mD5Hoz{aavW>n4`Gr#erJbbSIw5RIGMnCP?XX;bWsy$e}X5PMN z6Gp5JYryOQi#PqUXChgW_rZI+#s}y5FR^vuJsq0v-^KOBFm>m>j?n!~`q=?V=w5-4 za}z2lVa|=Nx%Hzm-1-se*l2@wt(rh8Lrox7Elm|t2zsWwZ;98esSK}#7=Ex4!Ykw& zgz#dnf$nB4DUnXhE%2&{z$-Z^KJItob<&2=yudYy4{52+dT{@`dM*a8e96V^`*{jl6+jPK;G=CO$TdS5ycu z-cO?HIl{0Ssjen)ZCb$6#zkZ)#tLf2!YaBn_N60PLXymjHhIqp*Z4Oyo+Jc3+R-q3R8PAtVhMF@LB`jhsb-LQ_(!NG^qmwS~9DFt5)xQKw6_2Z?7^pU;9uJg4;g) z0L!{5V(7vM6uyHZVmR<8)`d`VqAN8vmDQM99oDo|gM(Fmg|1Zcd0a7}4r#B}keFi4 zO~=EE>uWB2``rhBf50f}>gr_NclRc;r5<cAqJr$e+u?(l>o zr!&5M6YsxpE`tB6{*B;&4a71%0$szbZ|?8W@%Bolm>oB=oarR2j%#o=UgABa5zEWOBX*m8?Alhix+m1J=^N7{u+&Mm)8f57tBi{9?h<&_6dUk&mmac)G-hk9mE)AXHs4yzs)@XLu=xtMmRML6vb?!V1uQ=KD> zjp9XNANc=flzli#QLkuHCCJE2p~DrO242z0y6?wSH8>o0Rs_guI+L)=>0#G+da!Z+ zL|0wRJ@aM{TfD4dy7=v~hcenNUg#=Vv?Q1Ja!dhOS@L3Dx91KdH3t^pWDL@r1p)QB zN%fwR8*UcL7qaF~oN)h~@e}@dcd_4J+^sOTr*vTK?3rW7PM>U6LRwDmezZWng3E3{KP5LPDZVGEr^SecdIj0Hz# z`JmfUbNuG9rs*R(486T?N_MB{ai*!_C2y9uTlYE3;ak@pbC$Qf_a3#p+W!CJy>ble z^gHj;FBe9J@6w0ol;8cF()?VUZ~~X|yQz`_30S-9thrPZ{#TH~J_W$;%V!_Jpm>cj zV>{0+_6jFrhGQd0FuK`1;d{87KlwqM2lH!`Z3Q@w-JSeE?-c1!47)TLCw|CeUi)kU zCi6weE+h820BHd?xy7dxz)yOtcd`P0!f+rB9EWHo39Q+KZ4droH)`ao(>u=>3B#gs7BoWOckqskU-pb&a#K>o~V|$W#^Wt21hR%USTk|_UFJevOoHfGI z=Ff|8kbbbv$B+T6eWyT{8H)n@>;O^>E>rlk16ZvHGoJio0~}H6rv|WQaF5fIr+sQb zUT%R|h{mL0-dcJu-n3#K{a%)0laiu#3y!zmnm|f|Z@;#rztNYKW&M%$K7tRtTsni& z(H{cC(=dwi!V+1))3EZ)yn)F+)2vlGEGTNPo)OkQssiz280Q39b|`k~9FKum4 z0xiZ^UPupW&4UGxi+P<1ytcf+BjBlX&ynQwWY}q)Jp0eDpJ|vc>&}zU$z3%y!Of)O z0$NVa1<#R=!H#&>^5A*34|o;tKl(j-6yj?ZO^5sT`-pus-%)GZH)*x*R`7_#KG$Dl zU$AEqVQd>YneE|3wqtJNJ7oZ2w*}4(*kFqa;N6JemFpF7Zba>3D_`@)R*0QxA$Fvt zUSq}l+vrdwR)TsVvmP9RUmaH!Fr}q>*qsGwTE&}&oACzR265bWsb@jaCfERG9k^bK z*38CUQ6gT^>a!C$!U}G66;}vNb+#m4kT)peeTCmh5GE%1W;b?0P!bwZ#X3GTB6O*l zDh=}aFbzI*8`+N{_$=K6v}_E-q?(9X@R&)omb;_WYgZPtp za5L#%m2|d3Ek`1gsd*f`W9%jrn?2fn;>~}Q0}_^cjV{eb=>GwC+%CWX0C?JCU}Rum zV3eFSTV&(!cz&C&4DuWdAaM4ogb9rPSNTtXeI0u-kjufq1QG=RYH18{0C?JCU}Rw6 zNcy`LNHYAZ{8!DsjsYlw0zLo$kVOWx0C?JMlTTz^Q543%ckg|FR2Ef3q){;BrJz$5@AjAKh@&~T@aHXC^1ZKCXcM$I`yLlsdV zIa9#`=gQ6>y$-n3 zXt_fO-40r&PLdoSaeR!H%98Q;vH8LHBwGFqT3$f12u-`Ezc^Py#Vp|l^WK{efM3R_ z*+yVidDeBFV+Su;^Ds4S7Ld}L@tN6n*7(1oIYy*Ep-!!v5Owtix6C3Y`Oips*il}* zZqoKU@@t4BZaQ{-BsqGP`E8!_2xFYvH45-%FlNn3#vf?l z4)f=|9PX3b?<_tSFRTv(&>o{5SVgU}1>8P$5Zh|pi-K2q1dGsGTN zseyjS`%?${syOd_CAkZ5N)4$`IVbO-hXD$FTLtG4MlAAPK4L`BIij%Z&Cwg?sw(ef z74y!u^A*{fUM0+12h6jvs zOiWCZnAR~}Vfw{v#+=05#k`F981o|*1r`^U7M6RgGORhQCs^OH1+i^ld&DlqZp0qP zUdDcoqk>}#CmW{^XA9>B&TCw1Tz*_>TvNFAaoypT;P&F~;Xc5_#}mM_fad_uCtfMu z7~U@44ZL@F|M5xjS@9+CRq-w3SKwd4|3;ud;DDfj;5i`$As?X$LidFJ3D*dp5MdE1 z6L}))Cpt&;k(hy4jMxgX8{%T(PU0=%%f#PE7y)67#12U=$u!9|lJ}$%q$WuVNw-OF zkiI1SP9{gDO=geG6ImtM64?c^KjiG>667YyZIgQ?FD4%%KS4oAAxmM7!Z}4IMH|ID z#YKuwl&qAplx8WNQu?8+pzNVsq&!3Uj*5Val}d_ApUMH1XR2JPIjS>MkEni9lTmX~ zt5fGt&r(05VW2TjlR-00i$yC+YlAkMc7paS?Q=RTI#xO{Iy-a)bp3RDbkFHA=&9-D z>7CJ+&`;6dV!&YFVQ|3Uogs_i9wRfO7^6u>r;OQfKoMglV*_I!;|${-;|<2=OxR2u zOwvp`OjZHm5tDl+zf69anwc&#{b0spres!NcFEkxe2w`I0CXFPng9U+008g+LI4E- zJ^%#(0swjdhX8H>00A@r{Qv|20eIS-Q_C&{K@>eb?HSKlh=oPR%7WH2NJK>96(K@` zu(9dsX``9Z(%s^*_65Gd#xIBuU}NPIe1K1I>Q;HQ85^nG>QlGQxpnWYY5;wBfDNmq z6F@@K*unr;8W+%u8-s1k;nv_5jNrxKRt(|Y;5PJI9R|1K&Kfef1EbcX!CjcK-VE-> zL1Eb79^y-bd$C)1HTVgG_Nc+n@a%akBSMvy(XJ7q0*B^v?GpuvafU0_pjb!rI=H8m z;GswxH>ij)dRNJg$*VDrgC*jGYBl>3KgKCsY|$4IIoP596e+g3uHu|JpWFp{0%24* zC*+OO8dVM!sfnmkIjd~ErmTGQJ&Bo`Y?RIw?Wgin*DO*bv+7GGHL3jS67__>7>5l# z@TCezSXca(#hXY*Dq1Gl=&na{S|A?PeZ4+r=814CoP)1Erp&vsQ_Xv>?k%Ht784v7 zGFCJ=G|zo%6(n3 zcQ~eHuf($_xj&03@#w!~@&hCMrV%xx3>||Npk@hPSN6 z-JQW!fw7H_0>cTefspV9!Crvi8uS4OZox_58HWep6}t7u8~5_bU2>PZBZ`*zt-O6H6TNB#=lF$)u1<8tG(^Nfz1UkV_u<6i`SJ#gtG=D_YZrwzQ)? z9q33WI@5)&bfY^KG<2-kuv3PEaw_OSPkPatKJ=v@PF(b-5;qsKztm7)X`M`R%vxPkz=8(j&nYXNAml(yw zHZil28@!iT_Hu+@{Ny(WIL2LWbDUYsW(U>Wr-nP+<1r6-$Rj?6zxRwMJmmzw@XvPg zlIOg@&u6}}i8%zA%RFkSV;}X*r-2}igjm2r7V(M2ETM^|EN2-P+0RN=u!_}u;TxBD z#Ys+anb*AIjl@a3BuJtpNwTC!s-#J}WJsoDNj9fB!+9=nle3)T78^J!Ib7p9S0q>R zB%iH(mjWr2A}N*qGq^*+`sT!~_VKtP`-Ih%R;A6{ za<;Bp{{lIAr&0g_086+4$WmCb0RfI#xd;FV0AnDq0V71P10!&-7eyc-OSk|IQA@A} zQ(9QCG#jueSzu-$id9&!0wrOv0YzgYVz2@uM6wG31}d@)1_mm!6b1$=S+WEu2}M#w zvJ40ZDzOFuM6o0Rh*4OuK!{ke1_MN~CIN_1ShxfLh*+@(0Yq6@Sy{LN|Anvwjj;s) ML;wL%uV=LY00kR;TmS$7 literal 0 HcmV?d00001 diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png b/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png new file mode 100644 index 0000000000000000000000000000000000000000..8dfaed30ad56f7273d757737b7498bd2844d5557 GIT binary patch literal 84913 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 +

+ +
+
+
+
+
+
+

+ Bilan 2014 de l'hébergement +

+
+
+
+
+ Date + Sat 20 December 2014 + + Tags + Hébergement +
+

Déjà une année que j'ai migré l'hébergement de mes services d'un serveur à la +maison vers un hébergeur, en l'occurence OVH et son VPS Classic 2, sur un +container OpenVz dédié.

+

Les services hébergés sont à peu près les mêmes avec quelques nouveautés :

+
+

J'ai commencé à utilisé Wallabag depuis quelques +semaines, c'est la pièce qui me manquait dans mon processus de veille, entre le +lien récupéré en vitesse et avant l'ajout du lien dans mes favoris Shaarli s'il +en vaut la peine. Je l'utilise dans le cadre du projet +Framabag mais je prévois d'installer ma propre +instance d'ici peu.

+
+
+ + + + + +
Chargement...
+ + +
+
+
+
+
+ +
+ +
+ +
+ + + + diff --git a/demo/public/js/cors.js b/demo/public/js/cors.js new file mode 100644 index 0000000..1a49db4 --- /dev/null +++ b/demo/public/js/cors.js @@ -0,0 +1,46 @@ +// Create the XHR object. +function createCORSRequest(method, url) { + var xhr = new XMLHttpRequest(); + if ("withCredentials" in xhr) { + // XHR for Chrome/Firefox/Opera/Safari. + xhr.open(method, url, true); + } else if (typeof XDomainRequest != "undefined") { + // XDomainRequest for IE. + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + // CORS not supported. + xhr = null; + } + return xhr; +} + +// Helper method to parse the title tag from the response. +function getTitle(text) { + return text.match('(.*)?')[1]; +} + +// Make the actual CORS request. +function makeCorsRequest() { + // All HTML5 Rocks properties support CORS. + var url = 'http://sitea.fr:3000/comments'; + + var xhr = createCORSRequest('GET', url); + if (!xhr) { + alert('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var text = xhr.responseText; + //var title = getTitle(text); + alert('Response from CORS request to ' + url + ': ' + text); + }; + + xhr.onerror = function() { + alert('Woops, there was an error making the request.'); + }; + + xhr.send(); +} diff --git a/demo/public/js/cosysnode.js b/demo/public/js/cosysnode.js new file mode 100644 index 0000000..b76a178 --- /dev/null +++ b/demo/public/js/cosysnode.js @@ -0,0 +1,43 @@ +// Released under Apache license +// Copyright (c) 2015 Yannic ARNOUX + +// Create the XHR object. +function createCORSRequest(method, url) { + var xhr = new XMLHttpRequest(); + if ("withCredentials" in xhr) { + // XHR for Chrome/Firefox/Opera/Safari. + xhr.open(method, url, true); + } else if (typeof XDomainRequest != "undefined") { + // XDomainRequest for IE. + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + // CORS not supported. + xhr = null; + } + return xhr; +} + +function cosysload() { + var url = 'http://cosysnode.madyanne.fr:3000/comments'; + var xhr = createCORSRequest('GET', url); + if (!xhr) { + alert('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + var template = document.getElementById('template').innerHTML; + var rendered = Mustache.render(template, jsonResponse); + document.getElementById('cosys-comments').innerHTML = rendered; + }; + + xhr.onerror = function() { + alert('Woops, there was an error making the request.'); + }; + + xhr.send(); +} +window.onload = cosysload; diff --git a/demo/public/js/markdown.js b/demo/public/js/markdown.js new file mode 100644 index 0000000..65b04e4 --- /dev/null +++ b/demo/public/js/markdown.js @@ -0,0 +1,1740 @@ +// Released under MIT license +// Copyright (c) 2009-2010 Dominic Baggott +// Copyright (c) 2009-2010 Ash Berlin +// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) +// Date: 2013-09-15T16:12Z + +(function(expose) { + + + + + var MarkdownHelpers = {}; + + // For Spidermonkey based engines + function mk_block_toSource() { + return "Markdown.mk_block( " + + uneval(this.toString()) + + ", " + + uneval(this.trailing) + + ", " + + uneval(this.lineNumber) + + " )"; + } + + // node + function mk_block_inspect() { + var util = require("util"); + return "Markdown.mk_block( " + + util.inspect(this.toString()) + + ", " + + util.inspect(this.trailing) + + ", " + + util.inspect(this.lineNumber) + + " )"; + + } + + MarkdownHelpers.mk_block = function(block, trail, line) { + // Be helpful for default case in tests. + if ( arguments.length === 1 ) + trail = "\n\n"; + + // We actually need a String object, not a string primitive + /* jshint -W053 */ + var s = new String(block); + s.trailing = trail; + // To make it clear its not just a string + s.inspect = mk_block_inspect; + s.toSource = mk_block_toSource; + + if ( line !== undefined ) + s.lineNumber = line; + + return s; + }; + + + var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + // Don't mess with Array.prototype. Its not friendly + if ( Array.prototype.forEach ) { + MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { + return arr.forEach( cb, thisp ); + }; + } + else { + MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { + for (var i = 0; i < arr.length; i++) + cb.call(thisp || arr, arr[i], i, arr); + }; + } + + MarkdownHelpers.isEmpty = function isEmpty( obj ) { + for ( var key in obj ) { + if ( hasOwnProperty.call( obj, key ) ) + return false; + } + return true; + }; + + MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { + return isArray(jsonml) + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( isArray(jsonml[ 1 ]) ) + ? jsonml[ 1 ] + : undefined; + }; + + + + + /** + * class Markdown + * + * Markdown processing in Javascript done right. We have very particular views + * on what constitutes 'right' which include: + * + * - produces well-formed HTML (this means that em and strong nesting is + * important) + * + * - has an intermediate representation to allow processing of parsed data (We + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). + * + * - is easily extensible to add new dialects without having to rewrite the + * entire parsing mechanics + * + * - has a good test suite + * + * This implementation fulfills all of these (except that the test suite could + * do with expanding to automatically run all the fixtures from other Markdown + * implementations.) + * + * ##### Intermediate Representation + * + * *TODO* Talk about this :) Its JsonML, but document the node names we use. + * + * [JsonML]: http://jsonml.org/ "JSON Markup Language" + **/ + var Markdown = function(dialect) { + switch (typeof dialect) { + case "undefined": + this.dialect = Markdown.dialects.Gruber; + break; + case "object": + this.dialect = dialect; + break; + default: + if ( dialect in Markdown.dialects ) + this.dialect = Markdown.dialects[dialect]; + else + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); + break; + } + this.em_state = []; + this.strong_state = []; + this.debug_indent = ""; + }; + + /** + * Markdown.dialects + * + * Namespace of built-in dialects. + **/ + Markdown.dialects = {}; + + + + + // Imported functions + var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, + isArray = MarkdownHelpers.isArray; + + /** + * parse( markdown, [dialect] ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. + **/ + Markdown.parse = function( source, dialect ) { + // dialect will default if undefined + var md = new Markdown( dialect ); + return md.toTree( source ); + }; + + function count_lines( str ) { + var n = 0, + i = -1; + while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) + n++; + return n; + } + + // Internal - split source into rough blocks + Markdown.prototype.split_blocks = function splitBlocks( input ) { + input = input.replace(/(\r\n|\n|\r)/g, "\n"); + // [\s\S] matches _anything_ (newline or space) + // [^] is equivalent but doesn't work in IEs. + var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, + blocks = [], + m; + + var line_no = 1; + + if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { + // skip (but count) leading blank lines + line_no += count_lines( m[0] ); + re.lastIndex = m[0].length; + } + + while ( ( m = re.exec(input) ) !== null ) { + if (m[2] === "\n#") { + m[2] = "\n"; + re.lastIndex--; + } + blocks.push( mk_block( m[1], m[2], line_no ) ); + line_no += count_lines( m[0] ); + } + + return blocks; + }; + + /** + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] + * - block (String): the block to process + * - next (Array): the following blocks + * + * Process `block` and return an array of JsonML nodes representing `block`. + * + * It does this by asking each block level function in the dialect to process + * the block until one can. Succesful handling is indicated by returning an + * array (with zero or more JsonML nodes), failure by a false value. + * + * Blocks handlers are responsible for calling [[Markdown#processInline]] + * themselves as appropriate. + * + * If the blocks were split incorrectly or adjacent blocks need collapsing you + * can adjust `next` in place using shift/splice etc. + * + * If any of this default behaviour is not right for the dialect, you can + * define a `__call__` method on the dialect that will get invoked to handle + * the block processing. + */ + Markdown.prototype.processBlock = function processBlock( block, next ) { + var cbs = this.dialect.block, + ord = cbs.__order__; + + if ( "__call__" in cbs ) + return cbs.__call__.call(this, block, next); + + for ( var i = 0; i < ord.length; i++ ) { + //D:this.debug( "Testing", ord[i] ); + var res = cbs[ ord[i] ].call( this, block, next ); + if ( res ) { + //D:this.debug(" matched"); + if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) + this.debug(ord[i], "didn't return a proper array"); + //D:this.debug( "" ); + return res; + } + } + + // Uhoh! no match! Should we throw an error? + return []; + }; + + Markdown.prototype.processInline = function processInline( block ) { + return this.dialect.inline.__call__.call( this, String( block ) ); + }; + + /** + * Markdown#toTree( source ) -> JsonML + * - source (String): markdown source to parse + * + * Parse `source` into a JsonML tree representing the markdown document. + **/ + // custom_tree means set this.tree to `custom_tree` and restore old value on return + Markdown.prototype.toTree = function toTree( source, custom_root ) { + var blocks = source instanceof Array ? source : this.split_blocks( source ); + + // Make tree a member variable so its easier to mess with in extensions + var old_tree = this.tree; + try { + this.tree = custom_root || this.tree || [ "markdown" ]; + + blocks_loop: + while ( blocks.length ) { + var b = this.processBlock( blocks.shift(), blocks ); + + // Reference blocks and the like won't return any content + if ( !b.length ) + continue blocks_loop; + + this.tree.push.apply( this.tree, b ); + } + return this.tree; + } + finally { + if ( custom_root ) + this.tree = old_tree; + } + }; + + // Noop by default + Markdown.prototype.debug = function () { + var args = Array.prototype.slice.call( arguments); + args.unshift(this.debug_indent); + if ( typeof print !== "undefined" ) + print.apply( print, args ); + if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) + console.log.apply( null, args ); + }; + + Markdown.prototype.loop_re_over_block = function( re, block, cb ) { + // Dont use /g regexps with this + var m, + b = block.valueOf(); + + while ( b.length && (m = re.exec(b) ) !== null ) { + b = b.substr( m[0].length ); + cb.call(this, m); + } + return b; + }; + + // Build default order from insertion order. + Markdown.buildBlockOrder = function(d) { + var ord = []; + for ( var i in d ) { + if ( i === "__order__" || i === "__call__" ) + continue; + ord.push( i ); + } + d.__order__ = ord; + }; + + // Build patterns for inline matcher + Markdown.buildInlinePatterns = function(d) { + var patterns = []; + + for ( var i in d ) { + // __foo__ is reserved and not a pattern + if ( i.match( /^__.*__$/) ) + continue; + var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) + .replace( /\n/, "\\n" ); + patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); + } + + patterns = patterns.join("|"); + d.__patterns__ = patterns; + //print("patterns:", uneval( patterns ) ); + + var fn = d.__call__; + d.__call__ = function(text, pattern) { + if ( pattern !== undefined ) + return fn.call(this, text, pattern); + else + return fn.call(this, text, patterns); + }; + }; + + + + + var extract_attr = MarkdownHelpers.extract_attr; + + /** + * renderJsonML( jsonml[, options] ) -> String + * - jsonml (Array): JsonML array to render to XML + * - options (Object): options + * + * Converts the given JsonML into well-formed XML. + * + * The options currently understood are: + * + * - root (Boolean): wether or not the root node should be included in the + * output, or just its children. The default `false` is to not include the + * root itself. + */ + Markdown.renderJsonML = function( jsonml, options ) { + options = options || {}; + // include the root element in the rendered output? + options.root = options.root || false; + + var content = []; + + if ( options.root ) { + content.push( render_tree( jsonml ) ); + } + else { + jsonml.shift(); // get rid of the tag + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + jsonml.shift(); // get rid of the attributes + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + } + + return content.join( "\n\n" ); + }; + + + /** + * toHTMLTree( markdown, [dialect] ) -> JsonML + * toHTMLTree( md_tree ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Turn markdown into HTML, represented as a JsonML tree. If a string is given + * to this function, it is first parsed into a markdown tree by calling + * [[parse]]. + **/ + Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { + + // convert string input to an MD tree + if ( typeof input === "string" ) + input = this.parse( input, dialect ); + + // Now convert the MD tree to an HTML tree + + // remove references from the tree + var attrs = extract_attr( input ), + refs = {}; + + if ( attrs && attrs.references ) + refs = attrs.references; + + var html = convert_tree_to_html( input, refs , options ); + merge_text_nodes( html ); + return html; + }; + + /** + * toHTML( markdown, [dialect] ) -> String + * toHTML( md_tree ) -> String + * - markdown (String): markdown string to parse + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Take markdown (either as a string or as a JsonML tree) and run it through + * [[toHTMLTree]] then turn it into a well-formated HTML fragment. + **/ + Markdown.toHTML = function toHTML( source , dialect , options ) { + var input = this.toHTMLTree( source , dialect , options ); + + return this.renderJsonML( input ); + }; + + + function escapeHTML( text ) { + return text.replace( /&/g, "&" ) + .replace( //g, ">" ) + .replace( /"/g, """ ) + .replace( /'/g, "'" ); + } + + function render_tree( jsonml ) { + // basic case + if ( typeof jsonml === "string" ) + return escapeHTML( jsonml ); + + var tag = jsonml.shift(), + attributes = {}, + content = []; + + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + attributes = jsonml.shift(); + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + + var tag_attrs = ""; + for ( var a in attributes ) + tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; + + // be careful about adding whitespace here for inline elements + if ( tag === "img" || tag === "br" || tag === "hr" ) + return "<"+ tag + tag_attrs + "/>"; + else + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; + } + + function convert_tree_to_html( tree, references, options ) { + var i; + options = options || {}; + + // shallow clone + var jsonml = tree.slice( 0 ); + + if ( typeof options.preprocessTreeNode === "function" ) + jsonml = options.preprocessTreeNode(jsonml, references); + + // Clone attributes if they exist + var attrs = extract_attr( jsonml ); + if ( attrs ) { + jsonml[ 1 ] = {}; + for ( i in attrs ) { + jsonml[ 1 ][ i ] = attrs[ i ]; + } + attrs = jsonml[ 1 ]; + } + + // basic case + if ( typeof jsonml === "string" ) + return jsonml; + + // convert this node + switch ( jsonml[ 0 ] ) { + case "header": + jsonml[ 0 ] = "h" + jsonml[ 1 ].level; + delete jsonml[ 1 ].level; + break; + case "bulletlist": + jsonml[ 0 ] = "ul"; + break; + case "numberlist": + jsonml[ 0 ] = "ol"; + break; + case "listitem": + jsonml[ 0 ] = "li"; + break; + case "para": + jsonml[ 0 ] = "p"; + break; + case "markdown": + jsonml[ 0 ] = "html"; + if ( attrs ) + delete attrs.references; + break; + case "code_block": + jsonml[ 0 ] = "pre"; + i = attrs ? 2 : 1; + var code = [ "code" ]; + code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); + jsonml[ i ] = code; + break; + case "inlinecode": + jsonml[ 0 ] = "code"; + break; + case "img": + jsonml[ 1 ].src = jsonml[ 1 ].href; + delete jsonml[ 1 ].href; + break; + case "linebreak": + jsonml[ 0 ] = "br"; + break; + case "link": + jsonml[ 0 ] = "a"; + break; + case "link_ref": + jsonml[ 0 ] = "a"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.href = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + case "img_ref": + jsonml[ 0 ] = "img"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.src = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + } + + // convert all the children + i = 1; + + // deal with the attribute node, if it exists + if ( attrs ) { + // if there are keys, skip over it + for ( var key in jsonml[ 1 ] ) { + i = 2; + break; + } + // if there aren't, remove it + if ( i === 1 ) + jsonml.splice( i, 1 ); + } + + for ( ; i < jsonml.length; ++i ) { + jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); + } + + return jsonml; + } + + + // merges adjacent text nodes into a single node + function merge_text_nodes( jsonml ) { + // skip the tag name and attribute hash + var i = extract_attr( jsonml ) ? 2 : 1; + + while ( i < jsonml.length ) { + // if it's a string check the next item too + if ( typeof jsonml[ i ] === "string" ) { + if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { + // merge the second string into the first and remove it + jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; + } + else { + ++i; + } + } + // if it's not a string recurse + else { + merge_text_nodes( jsonml[ i ] ); + ++i; + } + } + }; + + + + var DialectHelpers = {}; + DialectHelpers.inline_until_char = function( text, want ) { + var consumed = 0, + nodes = []; + + while ( true ) { + if ( text.charAt( consumed ) === want ) { + // Found the character we were looking for + consumed++; + return [ consumed, nodes ]; + } + + if ( consumed >= text.length ) { + // No closing char found. Abort. + return null; + } + + var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); + consumed += res[ 0 ]; + // Add any returned nodes. + nodes.push.apply( nodes, res.slice( 1 ) ); + } + }; + + // Helper function to make sub-classing a dialect easier + DialectHelpers.subclassDialect = function( d ) { + function Block() {} + Block.prototype = d.block; + function Inline() {} + Inline.prototype = d.inline; + + return { block: new Block(), inline: new Inline() }; + }; + + + + + var forEach = MarkdownHelpers.forEach, + extract_attr = MarkdownHelpers.extract_attr, + mk_block = MarkdownHelpers.mk_block, + isEmpty = MarkdownHelpers.isEmpty, + inline_until_char = DialectHelpers.inline_until_char; + + /** + * Gruber dialect + * + * The default dialect that follows the rules set out by John Gruber's + * markdown.pl as closely as possible. Well actually we follow the behaviour of + * that script which in some places is not exactly what the syntax web page + * says. + **/ + var Gruber = { + block: { + atxHeader: function atxHeader( block, next ) { + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var header = [ "header", { level: m[ 1 ].length } ]; + Array.prototype.push.apply(header, this.processInline(m[ 2 ])); + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + setextHeader: function setextHeader( block, next ) { + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var level = ( m[ 2 ] === "=" ) ? 1 : 2, + header = [ "header", { level : level }, m[ 1 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + code: function code( block, next ) { + // | Foo + // |bar + // should be a code block followed by a paragraph. Fun + // + // There might also be adjacent code block to merge. + + var ret = [], + re = /^(?: {0,3}\t| {4})(.*)\n?/; + + // 4 spaces + content + if ( !block.match( re ) ) + return undefined; + + block_search: + do { + // Now pull out the rest of the lines + var b = this.loop_re_over_block( + re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); + + if ( b.length ) { + // Case alluded to in first comment. push it back on as a new block + next.unshift( mk_block(b, block.trailing) ); + break block_search; + } + else if ( next.length ) { + // Check the next block - it might be code too + if ( !next[0].match( re ) ) + break block_search; + + // Pull how how many blanks lines follow - minus two to account for .join + ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); + + block = next.shift(); + } + else { + break block_search; + } + } while ( true ); + + return [ [ "code_block", ret.join("\n") ] ]; + }, + + horizRule: function horizRule( block, next ) { + // this needs to find any hr in the block to handle abutting blocks + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); + + if ( !m ) + return undefined; + + var jsonml = [ [ "hr" ] ]; + + // if there's a leading abutting block, process it + if ( m[ 1 ] ) { + var contained = mk_block( m[ 1 ], "", block.lineNumber ); + jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); + } + + // if there's a trailing abutting block, stick it into next + if ( m[ 3 ] ) + next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); + + return jsonml; + }, + + // There are two types of lists. Tight and loose. Tight lists have no whitespace + // between the items (and result in text just in the
  • ) and loose lists, + // which have an empty line between list items, resulting in (one or more) + // paragraphs inside the
  • . + // + // There are all sorts weird edge cases about the original markdown.pl's + // handling of lists: + // + // * Nested lists are supposed to be indented by four chars per level. But + // if they aren't, you can get a nested list by indenting by less than + // four so long as the indent doesn't match an indent of an existing list + // item in the 'nest stack'. + // + // * The type of the list (bullet or number) is controlled just by the + // first item at the indent. Subsequent changes are ignored unless they + // are for nested lists + // + lists: (function( ) { + // Use a closure to hide a few variables. + var any_list = "[*+-]|\\d+\\.", + bullet_list = /[*+-]/, + // Capture leading indent as it matters for determining nested lists. + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), + indent_re = "(?: {0,3}\\t| {4})"; + + // TODO: Cache this regexp for certain depths. + // Create a regexp suitable for matching an li for a given stack depth + function regex_for_depth( depth ) { + + return new RegExp( + // m[1] = indent, m[2] = list_type + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + + // m[3] = cont + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" + ); + } + function expand_tab( input ) { + return input.replace( / {0,3}\t/g, " " ); + } + + // Add inline content `inline` to `li`. inline comes from processInline + // so is an array of content + function add(li, loose, inline, nl) { + if ( loose ) { + li.push( [ "para" ].concat(inline) ); + return; + } + // Hmmm, should this be any block level element or just paras? + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" + ? li[li.length -1] + : li; + + // If there is already some content in this list, add the new line in + if ( nl && li.length > 1 ) + inline.unshift(nl); + + for ( var i = 0; i < inline.length; i++ ) { + var what = inline[i], + is_str = typeof what === "string"; + if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) + add_to[ add_to.length-1 ] += what; + else + add_to.push( what ); + } + } + + // contained means have an indent greater than the current one. On + // *every* line in the block + function get_contained_blocks( depth, blocks ) { + + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), + ret = []; + + while ( blocks.length > 0 ) { + if ( re.exec( blocks[0] ) ) { + var b = blocks.shift(), + // Now remove that indent + x = b.replace( replace, ""); + + ret.push( mk_block( x, b.trailing, b.lineNumber ) ); + } + else + break; + } + return ret; + } + + // passed to stack.forEach to turn list items up the stack into paras + function paragraphify(s, i, stack) { + var list = s.list; + var last_li = list[list.length-1]; + + if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) + return; + if ( i + 1 === stack.length ) { + // Last stack frame + // Keep the same array, but replace the contents + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); + } + else { + var sublist = last_li.pop(); + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); + } + } + + // The matcher function + return function( block, next ) { + var m = block.match( is_list_re ); + if ( !m ) + return undefined; + + function make_list( m ) { + var list = bullet_list.exec( m[2] ) + ? ["bulletlist"] + : ["numberlist"]; + + stack.push( { list: list, indent: m[1] } ); + return list; + } + + + var stack = [], // Stack of lists for nesting. + list = make_list( m ), + last_li, + loose = false, + ret = [ stack[0].list ], + i; + + // Loop to search over block looking for inner block elements and loose lists + loose_search: + while ( true ) { + // Split into lines preserving new lines at end of line + var lines = block.split( /(?=\n)/ ); + + // We have to grab all lines for a li and call processInline on them + // once as there are some inline things that can span lines. + var li_accumulate = "", nl = ""; + + // Loop over the lines in this block looking for tight lists. + tight_search: + for ( var line_no = 0; line_no < lines.length; line_no++ ) { + nl = ""; + var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); + + + // TODO: really should cache this + var line_re = regex_for_depth( stack.length ); + + m = l.match( line_re ); + //print( "line:", uneval(l), "\nline match:", uneval(m) ); + + // We have a list item + if ( m[1] !== undefined ) { + // Process the previous list item, if any + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + m[1] = expand_tab( m[1] ); + var wanted_depth = Math.floor(m[1].length/4)+1; + //print( "want:", wanted_depth, "stack:", stack.length); + if ( wanted_depth > stack.length ) { + // Deep enough for a nested list outright + //print ( "new nested list" ); + list = make_list( m ); + last_li.push( list ); + last_li = list[1] = [ "listitem" ]; + } + else { + // We aren't deep enough to be strictly a new level. This is + // where Md.pl goes nuts. If the indent matches a level in the + // stack, put it there, else put it one deeper then the + // wanted_depth deserves. + var found = false; + for ( i = 0; i < stack.length; i++ ) { + if ( stack[ i ].indent !== m[1] ) + continue; + + list = stack[ i ].list; + stack.splice( i+1, stack.length - (i+1) ); + found = true; + break; + } + + if (!found) { + //print("not found. l:", uneval(l)); + wanted_depth++; + if ( wanted_depth <= stack.length ) { + stack.splice(wanted_depth, stack.length - wanted_depth); + //print("Desired depth now", wanted_depth, "stack:", stack.length); + list = stack[wanted_depth-1].list; + //print("list:", uneval(list) ); + } + else { + //print ("made new stack for messy indent"); + list = make_list(m); + last_li.push(list); + } + } + + //print( uneval(list), "last", list === stack[stack.length-1].list ); + last_li = [ "listitem" ]; + list.push(last_li); + } // end depth of shenegains + nl = ""; + } + + // Add content + if ( l.length > m[0].length ) + li_accumulate += nl + l.substr( m[0].length ); + } // tight_search + + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + // Look at the next block - we might have a loose list. Or an extra + // paragraph for the current li + var contained = get_contained_blocks( stack.length, next ); + + // Deal with code blocks or properly nested lists + if ( contained.length > 0 ) { + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + last_li.push.apply( last_li, this.toTree( contained, [] ) ); + } + + var next_block = next[0] && next[0].valueOf() || ""; + + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { + block = next.shift(); + + // Check for an HR following a list: features/lists/hr_abutting + var hr = this.dialect.block.horizRule( block, next ); + + if ( hr ) { + ret.push.apply(ret, hr); + break; + } + + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + loose = true; + continue loose_search; + } + break; + } // loose_search + + return ret; + }; + })(), + + blockquote: function blockquote( block, next ) { + if ( !block.match( /^>/m ) ) + return undefined; + + var jsonml = []; + + // separate out the leading abutting block, if any. I.e. in this case: + // + // a + // > b + // + if ( block[ 0 ] !== ">" ) { + var lines = block.split( /\n/ ), + prev = [], + line_no = block.lineNumber; + + // keep shifting lines until you find a crotchet + while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { + prev.push( lines.shift() ); + line_no++; + } + + var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); + jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); + // reassemble new block of just block quotes! + block = mk_block( lines.join( "\n" ), block.trailing, line_no ); + } + + + // if the next block is also a blockquote merge it in + while ( next.length && next[ 0 ][ 0 ] === ">" ) { + var b = next.shift(); + block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); + } + + // Strip off the leading "> " and re-process as a block. + var input = block.replace( /^> ?/gm, "" ), + old_tree = this.tree, + processedBlock = this.toTree( input, [ "blockquote" ] ), + attr = extract_attr( processedBlock ); + + // If any link references were found get rid of them + if ( attr && attr.references ) { + delete attr.references; + // And then remove the attribute object if it's empty + if ( isEmpty( attr ) ) + processedBlock.splice( 1, 1 ); + } + + jsonml.push( processedBlock ); + return jsonml; + }, + + referenceDefn: function referenceDefn( block, next) { + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; + // interesting matches are [ , ref_id, url, , title, title ] + + if ( !block.match(re) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var attrs = extract_attr( this.tree ); + + // make a references hash if it doesn't exist + if ( attrs.references === undefined ) + attrs.references = {}; + + var b = this.loop_re_over_block(re, block, function( m ) { + + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + var ref = attrs.references[ m[1].toLowerCase() ] = { + href: m[2] + }; + + if ( m[4] !== undefined ) + ref.title = m[4]; + else if ( m[5] !== undefined ) + ref.title = m[5]; + + } ); + + if ( b.length ) + next.unshift( mk_block( b, block.trailing ) ); + + return []; + }, + + para: function para( block ) { + // everything's a para! + return [ ["para"].concat( this.processInline( block ) ) ]; + } + }, + + inline: { + + __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { + var m, + res; + + patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; + var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); + + m = re.exec( text ); + if (!m) { + // Just boring text + return [ text.length, text ]; + } + else if ( m[1] ) { + // Some un-interesting text matched. Return that first + return [ m[1].length, m[1] ]; + } + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index ), m, previous_nodes || [] ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + return res; + }, + + __call__: function inline( text, patterns ) { + + var out = [], + res; + + function add(x) { + //D:self.debug(" adding output", uneval(x)); + if ( typeof x === "string" && typeof out[out.length-1] === "string" ) + out[ out.length-1 ] += x; + else + out.push(x); + } + + while ( text.length > 0 ) { + res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); + text = text.substr( res.shift() ); + forEach(res, add ); + } + + return out; + }, + + // These characters are intersting elsewhere, so have rules for them so that + // chunks of plain text blocks don't include them + "]": function () {}, + "}": function () {}, + + __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, + + "\\": function escaped( text ) { + // [ length of input processed, node/children to add... ] + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! + if ( this.dialect.inline.__escape__.exec( text ) ) + return [ 2, text.charAt( 1 ) ]; + else + // Not an esacpe + return [ 1, "\\" ]; + }, + + "![": function image( text ) { + + // Unlike images, alt text is plain text only. no other elements are + // allowed in there + + // ![Alt text](/path/to/img.jpg "Optional title") + // 1 2 3 4 <--- captures + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { alt: m[1], href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "img", attrs ] ]; + } + + // ![Alt text][id] + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); + + if ( m ) { + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; + } + + // Just consume the '![' + return [ 2, "![" ]; + }, + + "[": function link( text ) { + + var orig = String(text); + // Inline content is possible inside `link text` + var res = inline_until_char.call( this, text.substr(1), "]" ); + + // No closing ']' found. Just consume the [ + if ( !res ) + return [ 1, "[" ]; + + var consumed = 1 + res[ 0 ], + children = res[ 1 ], + link, + attrs; + + // At this point the first [...] has been parsed. See what follows to find + // out which kind of link we are (reference or direct url) + text = text.substr( consumed ); + + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 <--- captures + // This will capture up to the last paren in the block. We then pull + // back based on if there a matching ones in the url + // ([here](/url/(test)) + // The parens have to be balanced + var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); + if ( m ) { + var url = m[1]; + consumed += m[0].length; + + if ( url && url[0] === "<" && url[url.length-1] === ">" ) + url = url.substring( 1, url.length - 1 ); + + // If there is a title we don't have to worry about parens in the url + if ( !m[3] ) { + var open_parens = 1; // One open that isn't in the capture + for ( var len = 0; len < url.length; len++ ) { + switch ( url[len] ) { + case "(": + open_parens++; + break; + case ")": + if ( --open_parens === 0) { + consumed -= url.length - len; + url = url.substring(0, len); + } + break; + } + } + } + + // Process escapes only + url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; + + attrs = { href: url || "" }; + if ( m[3] !== undefined) + attrs.title = m[3]; + + link = [ "link", attrs ].concat( children ); + return [ consumed, link ]; + } + + // [Alt text][id] + // [Alt text] [id] + m = text.match( /^\s*\[(.*?)\]/ ); + + if ( m ) { + + consumed += m[ 0 ].length; + + // [links][] uses links as its reference + attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; + + link = [ "link_ref", attrs ].concat( children ); + + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion. + // Store the original so that conversion can revert if the ref isn't found. + return [ consumed, link ]; + } + + // [id] + // Only if id is plain (no formatting.) + if ( children.length === 1 && typeof children[0] === "string" ) { + + attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; + link = [ "link_ref", attrs, children[0] ]; + return [ consumed, link ]; + } + + // Just consume the "[" + return [ 1, "[" ]; + }, + + + "<": function autoLink( text ) { + var m; + + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { + if ( m[3] ) + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; + else if ( m[2] === "mailto" ) + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; + else + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; + } + + return [ 1, "<" ]; + }, + + "`": function inlineCode( text ) { + // Inline code block. as many backticks as you like to start it + // Always skip over the opening ticks. + var m = text.match( /(`+)(([\s\S]*?)\1)/ ); + + if ( m && m[2] ) + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; + else { + // TODO: No matching end code found - warn! + return [ 1, "`" ]; + } + }, + + " \n": function lineBreak() { + return [ 3, [ "linebreak" ] ]; + } + + } + }; + + // Meta Helper/generator method for em and strong handling + function strong_em( tag, md ) { + + var state_slot = tag + "_state", + other_slot = tag === "strong" ? "em_state" : "strong_state"; + + function CloseTag(len) { + this.len_after = len; + this.name = "close_" + md; + } + + return function ( text ) { + + if ( this[state_slot][0] === md ) { + // Most recent em is of this type + //D:this.debug("closing", md); + this[state_slot].shift(); + + // "Consume" everything to go back to the recrusion in the else-block below + return[ text.length, new CloseTag(text.length-md.length) ]; + } + else { + // Store a clone of the em/strong states + var other = this[other_slot].slice(), + state = this[state_slot].slice(); + + this[state_slot].unshift(md); + + //D:this.debug_indent += " "; + + // Recurse + var res = this.processInline( text.substr( md.length ) ); + //D:this.debug_indent = this.debug_indent.substr(2); + + var last = res[res.length - 1]; + + //D:this.debug("processInline from", tag + ": ", uneval( res ) ); + + var check = this[state_slot].shift(); + if ( last instanceof CloseTag ) { + res.pop(); + // We matched! Huzzah. + var consumed = text.length - last.len_after; + return [ consumed, [ tag ].concat(res) ]; + } + else { + // Restore the state of the other kind. We might have mistakenly closed it. + this[other_slot] = other; + this[state_slot] = state; + + // We can't reuse the processed result as it could have wrong parsing contexts in it. + return [ md.length, md ]; + } + } + }; // End returned function + } + + Gruber.inline["**"] = strong_em("strong", "**"); + Gruber.inline["__"] = strong_em("strong", "__"); + Gruber.inline["*"] = strong_em("em", "*"); + Gruber.inline["_"] = strong_em("em", "_"); + + Markdown.dialects.Gruber = Gruber; + Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); + + + + var Maruku = DialectHelpers.subclassDialect( Gruber ), + extract_attr = MarkdownHelpers.extract_attr, + forEach = MarkdownHelpers.forEach; + + Maruku.processMetaHash = function processMetaHash( meta_string ) { + var meta = split_meta_hash( meta_string ), + attr = {}; + + for ( var i = 0; i < meta.length; ++i ) { + // id: #foo + if ( /^#/.test( meta[ i ] ) ) + attr.id = meta[ i ].substring( 1 ); + // class: .foo + else if ( /^\./.test( meta[ i ] ) ) { + // if class already exists, append the new one + if ( attr["class"] ) + attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); + else + attr["class"] = meta[ i ].substring( 1 ); + } + // attribute: foo=bar + else if ( /\=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /\=/ ); + attr[ s[ 0 ] ] = s[ 1 ]; + } + } + + return attr; + }; + + function split_meta_hash( meta_string ) { + var meta = meta_string.split( "" ), + parts = [ "" ], + in_quotes = false; + + while ( meta.length ) { + var letter = meta.shift(); + switch ( letter ) { + case " " : + // if we're in a quoted section, keep it + if ( in_quotes ) + parts[ parts.length - 1 ] += letter; + // otherwise make a new part + else + parts.push( "" ); + break; + case "'" : + case '"' : + // reverse the quotes and move straight on + in_quotes = !in_quotes; + break; + case "\\" : + // shift off the next letter to be used straight away. + // it was escaped so we'll keep it whatever it is + letter = meta.shift(); + /* falls through */ + default : + parts[ parts.length - 1 ] += letter; + break; + } + } + + return parts; + } + + Maruku.block.document_meta = function document_meta( block ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) + return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var pairs = block.split( /\n/ ); + for ( var p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; + }; + + Maruku.block.block_meta = function block_meta( block ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) + return undefined; + + // process the meta hash + var attr = this.dialect.processMetaHash( m[ 2 ] ), + hash; + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ]; + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) + return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + return result; + }; + + Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, + list = [ "dl" ], + i, m; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) + blocks.push( next.shift() ); + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( i = 0; i < terms.length; ++i ) + list.push( [ "dt", terms[ i ] ] ); + + for ( i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; + }; + + // splits on unescaped instances of @ch. If @ch is not a character the result + // can be unpredictable + + Maruku.block.table = function table ( block ) { + + var _split_on_unescaped = function( s, ch ) { + ch = ch || '\\s'; + if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) + ch = '\\' + ch; + var res = [ ], + r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), + m; + while ( ( m = s.match( r ) ) ) { + res.push( m[1] ); + s = m[2]; + } + res.push(s); + return res; + }; + + var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, + // find at least an unescaped pipe in each line + no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, + i, + m; + if ( ( m = block.match( leading_pipe ) ) ) { + // remove leading pipes in contents + // (header and horizontal rule already have the leading pipe left out) + m[3] = m[3].replace(/^\s*\|/gm, ''); + } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { + return undefined; + } + + var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; + + // remove trailing pipes, then split on pipes + // (no escaped pipes are allowed in horizontal rule) + m[2] = m[2].replace(/\|\s*$/, '').split('|'); + + // process alignment + var html_attrs = [ ]; + forEach (m[2], function (s) { + if (s.match(/^\s*-+:\s*$/)) + html_attrs.push({align: "right"}); + else if (s.match(/^\s*:-+\s*$/)) + html_attrs.push({align: "left"}); + else if (s.match(/^\s*:-+:\s*$/)) + html_attrs.push({align: "center"}); + else + html_attrs.push({}); + }); + + // now for the header, avoid escaped pipes + m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); + for (i = 0; i < m[1].length; i++) { + table[1][1].push(['th', html_attrs[i] || {}].concat( + this.processInline(m[1][i].trim()))); + } + + // now for body contents + forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { + var html_row = ['tr']; + row = _split_on_unescaped(row, '|'); + for (i = 0; i < row.length; i++) + html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); + table[2].push(html_row); + }, this); + + return [table]; + }; + + Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) + return [ 2, "{:" ]; + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) + return [ 2, "{:" ]; + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) + return [ 2, "{:" ]; + + // attach the attributes to the preceeding element + var meta = this.dialect.processMetaHash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) + attr[ k ] = meta[ k ]; + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; + }; + + + Markdown.dialects.Maruku = Maruku; + Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; + Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + + +// Include all our depndencies and; + expose.Markdown = Markdown; + expose.parse = Markdown.parse; + expose.toHTML = Markdown.toHTML; + expose.toHTMLTree = Markdown.toHTMLTree; + expose.renderJsonML = Markdown.renderJsonML; + +})(function() { + window.markdown = {}; + return window.markdown; +}()); diff --git a/demo/public/js/mustache.js b/demo/public/js/mustache.js new file mode 100644 index 0000000..18d92a5 --- /dev/null +++ b/demo/public/js/mustache.js @@ -0,0 +1,586 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (global, factory) { + if (typeof exports === "object" && exports) { + factory(exports); // CommonJS + } else if (typeof define === "function" && define.amd) { + define(['exports'], factory); // AMD + } else { + factory(global.Mustache = {}); // + + + + + + +Open Sans Regular Specimen + + + + + + +
    + + + +
    + + +
    + +
    +
    +
    AaBb
    +
    +
    + +
    +
    A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
    +
    +
    +
    + + + + + + + + + + + + + + + + +
    10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    + +
    + +
    + + + +
    + + +
    +
    ◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
    body
    body
    body
    +
    + bodyOpen Sans Regular +
    +
    + bodyArial +
    +
    + bodyVerdana +
    +
    + bodyGeorgia +
    + + + +
    + + +
    + +
    +

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    + +
    +
    +
    +

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    + +
    + +
    + +
    +
    +

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    +
    +

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    + +
    + +
    + +
    +
    +

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    +
    + +
    + + + +
    +
    +

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    + +
    + +
    +
    +

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    +

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    + +
    +
    + +
    + +
    +
    +

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    +
    +

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    + +
    + +
    + +
    +
    +

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    +
    +
    + +
    + + + + +
    + +
    + +
    + +
    +

    Lorem Ipsum Dolor

    +

    Etiam porta sem malesuada magna mollis euismod

    + + +
    +
    +
    +
    +

    Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    + + +

    Pellentesque ornare sem

    + +

    Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

    + +

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    + +

    Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

    + +

    Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

    + +

    Cras mattis consectetur

    + +

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

    + +

    Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

    +
    + + +
    + +
    + + + + + + + + + +
    + +
    + +
    +
    +
    +

    Installing Webfonts

    + +

    Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

    + +

    1. Upload your webfonts

    +

    You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

    + +

    2. Include the webfont stylesheet

    +

    A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

    + + + +@font-face{ + font-family: 'MyWebFont'; + src: url('WebFont.eot'); + src: url('WebFont.eot?iefix') format('eot'), + url('WebFont.woff') format('woff'), + url('WebFont.ttf') format('truetype'), + url('WebFont.svg#webfont') format('svg'); +} + + +

    We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

    + <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> + +

    3. Modify your own stylesheet

    +

    To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

    +p { font-family: 'MyWebFont', Arial, sans-serif; } + +

    4. Test

    +

    Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

    +
    + + +
    + +
    + +
    + +
    + + diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..6bbc3cf58cb011a6b4bf3cb1612ce212608f7274 GIT binary patch literal 19836 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c537f8382a42986cc5e0d5a06b1460df4f0a5e25 GIT binary patch literal 38232 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 S)0Gzotc-JM?Hi4}ZTDZPiC;_r literal 0 HcmV?d00001 diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e231183dce4c7b452afc9e7799586fd285e146f4 GIT binary patch literal 22660 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 ML;wL%uV=LY00kR;TmS$7 literal 0 HcmV?d00001 diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png b/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png new file mode 100644 index 0000000000000000000000000000000000000000..8dfaed30ad56f7273d757737b7498bd2844d5557 GIT binary patch literal 84913 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 +

    + +
    +
    +
    +
    +
    +
    +

    + Bilan 2014 de l'hébergement +

    +
    +
    +
    +
    + Date + Sat 20 December 2014 + + Tags + Hébergement +
    +

    Déjà une année que j'ai migré l'hébergement de mes services d'un serveur à la +maison vers un hébergeur, en l'occurence OVH et son VPS Classic 2, sur un +container OpenVz dédié.

    +

    Les services hébergés sont à peu près les mêmes avec quelques nouveautés :

    +
    +

    J'ai commencé à utilisé Wallabag depuis quelques +semaines, c'est la pièce qui me manquait dans mon processus de veille, entre le +lien récupéré en vitesse et avant l'ajout du lien dans mes favoris Shaarli s'il +en vaut la peine. Je l'utilise dans le cadre du projet +Framabag mais je prévois d'installer ma propre +instance d'ici peu.

    +
    +
    + + + + + +
    Chargement...
    + + +
    +
    +
    +
    +
    + +
    + +
    + +
    + + + + diff --git a/demo/public/js/cors.js b/demo/public/js/cors.js new file mode 100644 index 0000000..1a49db4 --- /dev/null +++ b/demo/public/js/cors.js @@ -0,0 +1,46 @@ +// Create the XHR object. +function createCORSRequest(method, url) { + var xhr = new XMLHttpRequest(); + if ("withCredentials" in xhr) { + // XHR for Chrome/Firefox/Opera/Safari. + xhr.open(method, url, true); + } else if (typeof XDomainRequest != "undefined") { + // XDomainRequest for IE. + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + // CORS not supported. + xhr = null; + } + return xhr; +} + +// Helper method to parse the title tag from the response. +function getTitle(text) { + return text.match('(.*)?')[1]; +} + +// Make the actual CORS request. +function makeCorsRequest() { + // All HTML5 Rocks properties support CORS. + var url = 'http://sitea.fr:3000/comments'; + + var xhr = createCORSRequest('GET', url); + if (!xhr) { + alert('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var text = xhr.responseText; + //var title = getTitle(text); + alert('Response from CORS request to ' + url + ': ' + text); + }; + + xhr.onerror = function() { + alert('Woops, there was an error making the request.'); + }; + + xhr.send(); +} diff --git a/demo/public/js/cosysnode.js b/demo/public/js/cosysnode.js new file mode 100644 index 0000000..b76a178 --- /dev/null +++ b/demo/public/js/cosysnode.js @@ -0,0 +1,43 @@ +// Released under Apache license +// Copyright (c) 2015 Yannic ARNOUX + +// Create the XHR object. +function createCORSRequest(method, url) { + var xhr = new XMLHttpRequest(); + if ("withCredentials" in xhr) { + // XHR for Chrome/Firefox/Opera/Safari. + xhr.open(method, url, true); + } else if (typeof XDomainRequest != "undefined") { + // XDomainRequest for IE. + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + // CORS not supported. + xhr = null; + } + return xhr; +} + +function cosysload() { + var url = 'http://cosysnode.madyanne.fr:3000/comments'; + var xhr = createCORSRequest('GET', url); + if (!xhr) { + alert('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + var template = document.getElementById('template').innerHTML; + var rendered = Mustache.render(template, jsonResponse); + document.getElementById('cosys-comments').innerHTML = rendered; + }; + + xhr.onerror = function() { + alert('Woops, there was an error making the request.'); + }; + + xhr.send(); +} +window.onload = cosysload; diff --git a/demo/public/js/markdown.js b/demo/public/js/markdown.js new file mode 100644 index 0000000..65b04e4 --- /dev/null +++ b/demo/public/js/markdown.js @@ -0,0 +1,1740 @@ +// Released under MIT license +// Copyright (c) 2009-2010 Dominic Baggott +// Copyright (c) 2009-2010 Ash Berlin +// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) +// Date: 2013-09-15T16:12Z + +(function(expose) { + + + + + var MarkdownHelpers = {}; + + // For Spidermonkey based engines + function mk_block_toSource() { + return "Markdown.mk_block( " + + uneval(this.toString()) + + ", " + + uneval(this.trailing) + + ", " + + uneval(this.lineNumber) + + " )"; + } + + // node + function mk_block_inspect() { + var util = require("util"); + return "Markdown.mk_block( " + + util.inspect(this.toString()) + + ", " + + util.inspect(this.trailing) + + ", " + + util.inspect(this.lineNumber) + + " )"; + + } + + MarkdownHelpers.mk_block = function(block, trail, line) { + // Be helpful for default case in tests. + if ( arguments.length === 1 ) + trail = "\n\n"; + + // We actually need a String object, not a string primitive + /* jshint -W053 */ + var s = new String(block); + s.trailing = trail; + // To make it clear its not just a string + s.inspect = mk_block_inspect; + s.toSource = mk_block_toSource; + + if ( line !== undefined ) + s.lineNumber = line; + + return s; + }; + + + var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + // Don't mess with Array.prototype. Its not friendly + if ( Array.prototype.forEach ) { + MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { + return arr.forEach( cb, thisp ); + }; + } + else { + MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { + for (var i = 0; i < arr.length; i++) + cb.call(thisp || arr, arr[i], i, arr); + }; + } + + MarkdownHelpers.isEmpty = function isEmpty( obj ) { + for ( var key in obj ) { + if ( hasOwnProperty.call( obj, key ) ) + return false; + } + return true; + }; + + MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { + return isArray(jsonml) + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( isArray(jsonml[ 1 ]) ) + ? jsonml[ 1 ] + : undefined; + }; + + + + + /** + * class Markdown + * + * Markdown processing in Javascript done right. We have very particular views + * on what constitutes 'right' which include: + * + * - produces well-formed HTML (this means that em and strong nesting is + * important) + * + * - has an intermediate representation to allow processing of parsed data (We + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). + * + * - is easily extensible to add new dialects without having to rewrite the + * entire parsing mechanics + * + * - has a good test suite + * + * This implementation fulfills all of these (except that the test suite could + * do with expanding to automatically run all the fixtures from other Markdown + * implementations.) + * + * ##### Intermediate Representation + * + * *TODO* Talk about this :) Its JsonML, but document the node names we use. + * + * [JsonML]: http://jsonml.org/ "JSON Markup Language" + **/ + var Markdown = function(dialect) { + switch (typeof dialect) { + case "undefined": + this.dialect = Markdown.dialects.Gruber; + break; + case "object": + this.dialect = dialect; + break; + default: + if ( dialect in Markdown.dialects ) + this.dialect = Markdown.dialects[dialect]; + else + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); + break; + } + this.em_state = []; + this.strong_state = []; + this.debug_indent = ""; + }; + + /** + * Markdown.dialects + * + * Namespace of built-in dialects. + **/ + Markdown.dialects = {}; + + + + + // Imported functions + var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, + isArray = MarkdownHelpers.isArray; + + /** + * parse( markdown, [dialect] ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. + **/ + Markdown.parse = function( source, dialect ) { + // dialect will default if undefined + var md = new Markdown( dialect ); + return md.toTree( source ); + }; + + function count_lines( str ) { + var n = 0, + i = -1; + while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) + n++; + return n; + } + + // Internal - split source into rough blocks + Markdown.prototype.split_blocks = function splitBlocks( input ) { + input = input.replace(/(\r\n|\n|\r)/g, "\n"); + // [\s\S] matches _anything_ (newline or space) + // [^] is equivalent but doesn't work in IEs. + var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, + blocks = [], + m; + + var line_no = 1; + + if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { + // skip (but count) leading blank lines + line_no += count_lines( m[0] ); + re.lastIndex = m[0].length; + } + + while ( ( m = re.exec(input) ) !== null ) { + if (m[2] === "\n#") { + m[2] = "\n"; + re.lastIndex--; + } + blocks.push( mk_block( m[1], m[2], line_no ) ); + line_no += count_lines( m[0] ); + } + + return blocks; + }; + + /** + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] + * - block (String): the block to process + * - next (Array): the following blocks + * + * Process `block` and return an array of JsonML nodes representing `block`. + * + * It does this by asking each block level function in the dialect to process + * the block until one can. Succesful handling is indicated by returning an + * array (with zero or more JsonML nodes), failure by a false value. + * + * Blocks handlers are responsible for calling [[Markdown#processInline]] + * themselves as appropriate. + * + * If the blocks were split incorrectly or adjacent blocks need collapsing you + * can adjust `next` in place using shift/splice etc. + * + * If any of this default behaviour is not right for the dialect, you can + * define a `__call__` method on the dialect that will get invoked to handle + * the block processing. + */ + Markdown.prototype.processBlock = function processBlock( block, next ) { + var cbs = this.dialect.block, + ord = cbs.__order__; + + if ( "__call__" in cbs ) + return cbs.__call__.call(this, block, next); + + for ( var i = 0; i < ord.length; i++ ) { + //D:this.debug( "Testing", ord[i] ); + var res = cbs[ ord[i] ].call( this, block, next ); + if ( res ) { + //D:this.debug(" matched"); + if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) + this.debug(ord[i], "didn't return a proper array"); + //D:this.debug( "" ); + return res; + } + } + + // Uhoh! no match! Should we throw an error? + return []; + }; + + Markdown.prototype.processInline = function processInline( block ) { + return this.dialect.inline.__call__.call( this, String( block ) ); + }; + + /** + * Markdown#toTree( source ) -> JsonML + * - source (String): markdown source to parse + * + * Parse `source` into a JsonML tree representing the markdown document. + **/ + // custom_tree means set this.tree to `custom_tree` and restore old value on return + Markdown.prototype.toTree = function toTree( source, custom_root ) { + var blocks = source instanceof Array ? source : this.split_blocks( source ); + + // Make tree a member variable so its easier to mess with in extensions + var old_tree = this.tree; + try { + this.tree = custom_root || this.tree || [ "markdown" ]; + + blocks_loop: + while ( blocks.length ) { + var b = this.processBlock( blocks.shift(), blocks ); + + // Reference blocks and the like won't return any content + if ( !b.length ) + continue blocks_loop; + + this.tree.push.apply( this.tree, b ); + } + return this.tree; + } + finally { + if ( custom_root ) + this.tree = old_tree; + } + }; + + // Noop by default + Markdown.prototype.debug = function () { + var args = Array.prototype.slice.call( arguments); + args.unshift(this.debug_indent); + if ( typeof print !== "undefined" ) + print.apply( print, args ); + if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) + console.log.apply( null, args ); + }; + + Markdown.prototype.loop_re_over_block = function( re, block, cb ) { + // Dont use /g regexps with this + var m, + b = block.valueOf(); + + while ( b.length && (m = re.exec(b) ) !== null ) { + b = b.substr( m[0].length ); + cb.call(this, m); + } + return b; + }; + + // Build default order from insertion order. + Markdown.buildBlockOrder = function(d) { + var ord = []; + for ( var i in d ) { + if ( i === "__order__" || i === "__call__" ) + continue; + ord.push( i ); + } + d.__order__ = ord; + }; + + // Build patterns for inline matcher + Markdown.buildInlinePatterns = function(d) { + var patterns = []; + + for ( var i in d ) { + // __foo__ is reserved and not a pattern + if ( i.match( /^__.*__$/) ) + continue; + var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) + .replace( /\n/, "\\n" ); + patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); + } + + patterns = patterns.join("|"); + d.__patterns__ = patterns; + //print("patterns:", uneval( patterns ) ); + + var fn = d.__call__; + d.__call__ = function(text, pattern) { + if ( pattern !== undefined ) + return fn.call(this, text, pattern); + else + return fn.call(this, text, patterns); + }; + }; + + + + + var extract_attr = MarkdownHelpers.extract_attr; + + /** + * renderJsonML( jsonml[, options] ) -> String + * - jsonml (Array): JsonML array to render to XML + * - options (Object): options + * + * Converts the given JsonML into well-formed XML. + * + * The options currently understood are: + * + * - root (Boolean): wether or not the root node should be included in the + * output, or just its children. The default `false` is to not include the + * root itself. + */ + Markdown.renderJsonML = function( jsonml, options ) { + options = options || {}; + // include the root element in the rendered output? + options.root = options.root || false; + + var content = []; + + if ( options.root ) { + content.push( render_tree( jsonml ) ); + } + else { + jsonml.shift(); // get rid of the tag + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + jsonml.shift(); // get rid of the attributes + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + } + + return content.join( "\n\n" ); + }; + + + /** + * toHTMLTree( markdown, [dialect] ) -> JsonML + * toHTMLTree( md_tree ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Turn markdown into HTML, represented as a JsonML tree. If a string is given + * to this function, it is first parsed into a markdown tree by calling + * [[parse]]. + **/ + Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { + + // convert string input to an MD tree + if ( typeof input === "string" ) + input = this.parse( input, dialect ); + + // Now convert the MD tree to an HTML tree + + // remove references from the tree + var attrs = extract_attr( input ), + refs = {}; + + if ( attrs && attrs.references ) + refs = attrs.references; + + var html = convert_tree_to_html( input, refs , options ); + merge_text_nodes( html ); + return html; + }; + + /** + * toHTML( markdown, [dialect] ) -> String + * toHTML( md_tree ) -> String + * - markdown (String): markdown string to parse + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Take markdown (either as a string or as a JsonML tree) and run it through + * [[toHTMLTree]] then turn it into a well-formated HTML fragment. + **/ + Markdown.toHTML = function toHTML( source , dialect , options ) { + var input = this.toHTMLTree( source , dialect , options ); + + return this.renderJsonML( input ); + }; + + + function escapeHTML( text ) { + return text.replace( /&/g, "&" ) + .replace( //g, ">" ) + .replace( /"/g, """ ) + .replace( /'/g, "'" ); + } + + function render_tree( jsonml ) { + // basic case + if ( typeof jsonml === "string" ) + return escapeHTML( jsonml ); + + var tag = jsonml.shift(), + attributes = {}, + content = []; + + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + attributes = jsonml.shift(); + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + + var tag_attrs = ""; + for ( var a in attributes ) + tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; + + // be careful about adding whitespace here for inline elements + if ( tag === "img" || tag === "br" || tag === "hr" ) + return "<"+ tag + tag_attrs + "/>"; + else + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; + } + + function convert_tree_to_html( tree, references, options ) { + var i; + options = options || {}; + + // shallow clone + var jsonml = tree.slice( 0 ); + + if ( typeof options.preprocessTreeNode === "function" ) + jsonml = options.preprocessTreeNode(jsonml, references); + + // Clone attributes if they exist + var attrs = extract_attr( jsonml ); + if ( attrs ) { + jsonml[ 1 ] = {}; + for ( i in attrs ) { + jsonml[ 1 ][ i ] = attrs[ i ]; + } + attrs = jsonml[ 1 ]; + } + + // basic case + if ( typeof jsonml === "string" ) + return jsonml; + + // convert this node + switch ( jsonml[ 0 ] ) { + case "header": + jsonml[ 0 ] = "h" + jsonml[ 1 ].level; + delete jsonml[ 1 ].level; + break; + case "bulletlist": + jsonml[ 0 ] = "ul"; + break; + case "numberlist": + jsonml[ 0 ] = "ol"; + break; + case "listitem": + jsonml[ 0 ] = "li"; + break; + case "para": + jsonml[ 0 ] = "p"; + break; + case "markdown": + jsonml[ 0 ] = "html"; + if ( attrs ) + delete attrs.references; + break; + case "code_block": + jsonml[ 0 ] = "pre"; + i = attrs ? 2 : 1; + var code = [ "code" ]; + code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); + jsonml[ i ] = code; + break; + case "inlinecode": + jsonml[ 0 ] = "code"; + break; + case "img": + jsonml[ 1 ].src = jsonml[ 1 ].href; + delete jsonml[ 1 ].href; + break; + case "linebreak": + jsonml[ 0 ] = "br"; + break; + case "link": + jsonml[ 0 ] = "a"; + break; + case "link_ref": + jsonml[ 0 ] = "a"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.href = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + case "img_ref": + jsonml[ 0 ] = "img"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.src = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + } + + // convert all the children + i = 1; + + // deal with the attribute node, if it exists + if ( attrs ) { + // if there are keys, skip over it + for ( var key in jsonml[ 1 ] ) { + i = 2; + break; + } + // if there aren't, remove it + if ( i === 1 ) + jsonml.splice( i, 1 ); + } + + for ( ; i < jsonml.length; ++i ) { + jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); + } + + return jsonml; + } + + + // merges adjacent text nodes into a single node + function merge_text_nodes( jsonml ) { + // skip the tag name and attribute hash + var i = extract_attr( jsonml ) ? 2 : 1; + + while ( i < jsonml.length ) { + // if it's a string check the next item too + if ( typeof jsonml[ i ] === "string" ) { + if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { + // merge the second string into the first and remove it + jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; + } + else { + ++i; + } + } + // if it's not a string recurse + else { + merge_text_nodes( jsonml[ i ] ); + ++i; + } + } + }; + + + + var DialectHelpers = {}; + DialectHelpers.inline_until_char = function( text, want ) { + var consumed = 0, + nodes = []; + + while ( true ) { + if ( text.charAt( consumed ) === want ) { + // Found the character we were looking for + consumed++; + return [ consumed, nodes ]; + } + + if ( consumed >= text.length ) { + // No closing char found. Abort. + return null; + } + + var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); + consumed += res[ 0 ]; + // Add any returned nodes. + nodes.push.apply( nodes, res.slice( 1 ) ); + } + }; + + // Helper function to make sub-classing a dialect easier + DialectHelpers.subclassDialect = function( d ) { + function Block() {} + Block.prototype = d.block; + function Inline() {} + Inline.prototype = d.inline; + + return { block: new Block(), inline: new Inline() }; + }; + + + + + var forEach = MarkdownHelpers.forEach, + extract_attr = MarkdownHelpers.extract_attr, + mk_block = MarkdownHelpers.mk_block, + isEmpty = MarkdownHelpers.isEmpty, + inline_until_char = DialectHelpers.inline_until_char; + + /** + * Gruber dialect + * + * The default dialect that follows the rules set out by John Gruber's + * markdown.pl as closely as possible. Well actually we follow the behaviour of + * that script which in some places is not exactly what the syntax web page + * says. + **/ + var Gruber = { + block: { + atxHeader: function atxHeader( block, next ) { + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var header = [ "header", { level: m[ 1 ].length } ]; + Array.prototype.push.apply(header, this.processInline(m[ 2 ])); + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + setextHeader: function setextHeader( block, next ) { + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var level = ( m[ 2 ] === "=" ) ? 1 : 2, + header = [ "header", { level : level }, m[ 1 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + code: function code( block, next ) { + // | Foo + // |bar + // should be a code block followed by a paragraph. Fun + // + // There might also be adjacent code block to merge. + + var ret = [], + re = /^(?: {0,3}\t| {4})(.*)\n?/; + + // 4 spaces + content + if ( !block.match( re ) ) + return undefined; + + block_search: + do { + // Now pull out the rest of the lines + var b = this.loop_re_over_block( + re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); + + if ( b.length ) { + // Case alluded to in first comment. push it back on as a new block + next.unshift( mk_block(b, block.trailing) ); + break block_search; + } + else if ( next.length ) { + // Check the next block - it might be code too + if ( !next[0].match( re ) ) + break block_search; + + // Pull how how many blanks lines follow - minus two to account for .join + ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); + + block = next.shift(); + } + else { + break block_search; + } + } while ( true ); + + return [ [ "code_block", ret.join("\n") ] ]; + }, + + horizRule: function horizRule( block, next ) { + // this needs to find any hr in the block to handle abutting blocks + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); + + if ( !m ) + return undefined; + + var jsonml = [ [ "hr" ] ]; + + // if there's a leading abutting block, process it + if ( m[ 1 ] ) { + var contained = mk_block( m[ 1 ], "", block.lineNumber ); + jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); + } + + // if there's a trailing abutting block, stick it into next + if ( m[ 3 ] ) + next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); + + return jsonml; + }, + + // There are two types of lists. Tight and loose. Tight lists have no whitespace + // between the items (and result in text just in the

    9?uG;6JXDPtu17tlup#cGgk~?1Nc5Z5wH} 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­ml+D=R+taKLSXsz@g$ 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 Hi#`1x$O7_n literal 0 HcmV?d00001 diff --git a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg b/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a23176c70ab449fcdd43b5da799197a945563ae GIT binary patch literal 2637 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/framasoft.png b/demo/public/img/framasoft.png new file mode 100644 index 0000000000000000000000000000000000000000..71e74c308106e6833c9a841ec4edbf07f7ecccfb GIT binary patch literal 19259 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/id-150.jpg b/demo/public/img/id-150.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebc872a5c1180aceda7936ccfb7361ac9a4f68a4 GIT binary patch literal 19264 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/license.png b/demo/public/img/license.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2a0f10722d3fabffe8af5d5eed09866e25909a GIT binary patch literal 5460 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 Ng2}4L)JmC!{ST_+RpI~u literal 0 HcmV?d00001 diff --git a/demo/public/index.html b/demo/public/index.html new file mode 100644 index 0000000..b8ddee6 --- /dev/null +++ b/demo/public/index.html @@ -0,0 +1,208 @@ + + + + + + + + Bilan 2014 de l'hébergement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

  • ) and loose lists, + // which have an empty line between list items, resulting in (one or more) + // paragraphs inside the
  • . + // + // There are all sorts weird edge cases about the original markdown.pl's + // handling of lists: + // + // * Nested lists are supposed to be indented by four chars per level. But + // if they aren't, you can get a nested list by indenting by less than + // four so long as the indent doesn't match an indent of an existing list + // item in the 'nest stack'. + // + // * The type of the list (bullet or number) is controlled just by the + // first item at the indent. Subsequent changes are ignored unless they + // are for nested lists + // + lists: (function( ) { + // Use a closure to hide a few variables. + var any_list = "[*+-]|\\d+\\.", + bullet_list = /[*+-]/, + // Capture leading indent as it matters for determining nested lists. + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), + indent_re = "(?: {0,3}\\t| {4})"; + + // TODO: Cache this regexp for certain depths. + // Create a regexp suitable for matching an li for a given stack depth + function regex_for_depth( depth ) { + + return new RegExp( + // m[1] = indent, m[2] = list_type + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + + // m[3] = cont + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" + ); + } + function expand_tab( input ) { + return input.replace( / {0,3}\t/g, " " ); + } + + // Add inline content `inline` to `li`. inline comes from processInline + // so is an array of content + function add(li, loose, inline, nl) { + if ( loose ) { + li.push( [ "para" ].concat(inline) ); + return; + } + // Hmmm, should this be any block level element or just paras? + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" + ? li[li.length -1] + : li; + + // If there is already some content in this list, add the new line in + if ( nl && li.length > 1 ) + inline.unshift(nl); + + for ( var i = 0; i < inline.length; i++ ) { + var what = inline[i], + is_str = typeof what === "string"; + if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) + add_to[ add_to.length-1 ] += what; + else + add_to.push( what ); + } + } + + // contained means have an indent greater than the current one. On + // *every* line in the block + function get_contained_blocks( depth, blocks ) { + + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), + ret = []; + + while ( blocks.length > 0 ) { + if ( re.exec( blocks[0] ) ) { + var b = blocks.shift(), + // Now remove that indent + x = b.replace( replace, ""); + + ret.push( mk_block( x, b.trailing, b.lineNumber ) ); + } + else + break; + } + return ret; + } + + // passed to stack.forEach to turn list items up the stack into paras + function paragraphify(s, i, stack) { + var list = s.list; + var last_li = list[list.length-1]; + + if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) + return; + if ( i + 1 === stack.length ) { + // Last stack frame + // Keep the same array, but replace the contents + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); + } + else { + var sublist = last_li.pop(); + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); + } + } + + // The matcher function + return function( block, next ) { + var m = block.match( is_list_re ); + if ( !m ) + return undefined; + + function make_list( m ) { + var list = bullet_list.exec( m[2] ) + ? ["bulletlist"] + : ["numberlist"]; + + stack.push( { list: list, indent: m[1] } ); + return list; + } + + + var stack = [], // Stack of lists for nesting. + list = make_list( m ), + last_li, + loose = false, + ret = [ stack[0].list ], + i; + + // Loop to search over block looking for inner block elements and loose lists + loose_search: + while ( true ) { + // Split into lines preserving new lines at end of line + var lines = block.split( /(?=\n)/ ); + + // We have to grab all lines for a li and call processInline on them + // once as there are some inline things that can span lines. + var li_accumulate = "", nl = ""; + + // Loop over the lines in this block looking for tight lists. + tight_search: + for ( var line_no = 0; line_no < lines.length; line_no++ ) { + nl = ""; + var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); + + + // TODO: really should cache this + var line_re = regex_for_depth( stack.length ); + + m = l.match( line_re ); + //print( "line:", uneval(l), "\nline match:", uneval(m) ); + + // We have a list item + if ( m[1] !== undefined ) { + // Process the previous list item, if any + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + m[1] = expand_tab( m[1] ); + var wanted_depth = Math.floor(m[1].length/4)+1; + //print( "want:", wanted_depth, "stack:", stack.length); + if ( wanted_depth > stack.length ) { + // Deep enough for a nested list outright + //print ( "new nested list" ); + list = make_list( m ); + last_li.push( list ); + last_li = list[1] = [ "listitem" ]; + } + else { + // We aren't deep enough to be strictly a new level. This is + // where Md.pl goes nuts. If the indent matches a level in the + // stack, put it there, else put it one deeper then the + // wanted_depth deserves. + var found = false; + for ( i = 0; i < stack.length; i++ ) { + if ( stack[ i ].indent !== m[1] ) + continue; + + list = stack[ i ].list; + stack.splice( i+1, stack.length - (i+1) ); + found = true; + break; + } + + if (!found) { + //print("not found. l:", uneval(l)); + wanted_depth++; + if ( wanted_depth <= stack.length ) { + stack.splice(wanted_depth, stack.length - wanted_depth); + //print("Desired depth now", wanted_depth, "stack:", stack.length); + list = stack[wanted_depth-1].list; + //print("list:", uneval(list) ); + } + else { + //print ("made new stack for messy indent"); + list = make_list(m); + last_li.push(list); + } + } + + //print( uneval(list), "last", list === stack[stack.length-1].list ); + last_li = [ "listitem" ]; + list.push(last_li); + } // end depth of shenegains + nl = ""; + } + + // Add content + if ( l.length > m[0].length ) + li_accumulate += nl + l.substr( m[0].length ); + } // tight_search + + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + // Look at the next block - we might have a loose list. Or an extra + // paragraph for the current li + var contained = get_contained_blocks( stack.length, next ); + + // Deal with code blocks or properly nested lists + if ( contained.length > 0 ) { + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + last_li.push.apply( last_li, this.toTree( contained, [] ) ); + } + + var next_block = next[0] && next[0].valueOf() || ""; + + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { + block = next.shift(); + + // Check for an HR following a list: features/lists/hr_abutting + var hr = this.dialect.block.horizRule( block, next ); + + if ( hr ) { + ret.push.apply(ret, hr); + break; + } + + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + loose = true; + continue loose_search; + } + break; + } // loose_search + + return ret; + }; + })(), + + blockquote: function blockquote( block, next ) { + if ( !block.match( /^>/m ) ) + return undefined; + + var jsonml = []; + + // separate out the leading abutting block, if any. I.e. in this case: + // + // a + // > b + // + if ( block[ 0 ] !== ">" ) { + var lines = block.split( /\n/ ), + prev = [], + line_no = block.lineNumber; + + // keep shifting lines until you find a crotchet + while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { + prev.push( lines.shift() ); + line_no++; + } + + var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); + jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); + // reassemble new block of just block quotes! + block = mk_block( lines.join( "\n" ), block.trailing, line_no ); + } + + + // if the next block is also a blockquote merge it in + while ( next.length && next[ 0 ][ 0 ] === ">" ) { + var b = next.shift(); + block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); + } + + // Strip off the leading "> " and re-process as a block. + var input = block.replace( /^> ?/gm, "" ), + old_tree = this.tree, + processedBlock = this.toTree( input, [ "blockquote" ] ), + attr = extract_attr( processedBlock ); + + // If any link references were found get rid of them + if ( attr && attr.references ) { + delete attr.references; + // And then remove the attribute object if it's empty + if ( isEmpty( attr ) ) + processedBlock.splice( 1, 1 ); + } + + jsonml.push( processedBlock ); + return jsonml; + }, + + referenceDefn: function referenceDefn( block, next) { + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; + // interesting matches are [ , ref_id, url, , title, title ] + + if ( !block.match(re) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var attrs = extract_attr( this.tree ); + + // make a references hash if it doesn't exist + if ( attrs.references === undefined ) + attrs.references = {}; + + var b = this.loop_re_over_block(re, block, function( m ) { + + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + var ref = attrs.references[ m[1].toLowerCase() ] = { + href: m[2] + }; + + if ( m[4] !== undefined ) + ref.title = m[4]; + else if ( m[5] !== undefined ) + ref.title = m[5]; + + } ); + + if ( b.length ) + next.unshift( mk_block( b, block.trailing ) ); + + return []; + }, + + para: function para( block ) { + // everything's a para! + return [ ["para"].concat( this.processInline( block ) ) ]; + } + }, + + inline: { + + __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { + var m, + res; + + patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; + var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); + + m = re.exec( text ); + if (!m) { + // Just boring text + return [ text.length, text ]; + } + else if ( m[1] ) { + // Some un-interesting text matched. Return that first + return [ m[1].length, m[1] ]; + } + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index ), m, previous_nodes || [] ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + return res; + }, + + __call__: function inline( text, patterns ) { + + var out = [], + res; + + function add(x) { + //D:self.debug(" adding output", uneval(x)); + if ( typeof x === "string" && typeof out[out.length-1] === "string" ) + out[ out.length-1 ] += x; + else + out.push(x); + } + + while ( text.length > 0 ) { + res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); + text = text.substr( res.shift() ); + forEach(res, add ); + } + + return out; + }, + + // These characters are intersting elsewhere, so have rules for them so that + // chunks of plain text blocks don't include them + "]": function () {}, + "}": function () {}, + + __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, + + "\\": function escaped( text ) { + // [ length of input processed, node/children to add... ] + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! + if ( this.dialect.inline.__escape__.exec( text ) ) + return [ 2, text.charAt( 1 ) ]; + else + // Not an esacpe + return [ 1, "\\" ]; + }, + + "![": function image( text ) { + + // Unlike images, alt text is plain text only. no other elements are + // allowed in there + + // ![Alt text](/path/to/img.jpg "Optional title") + // 1 2 3 4 <--- captures + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { alt: m[1], href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "img", attrs ] ]; + } + + // ![Alt text][id] + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); + + if ( m ) { + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; + } + + // Just consume the '![' + return [ 2, "![" ]; + }, + + "[": function link( text ) { + + var orig = String(text); + // Inline content is possible inside `link text` + var res = inline_until_char.call( this, text.substr(1), "]" ); + + // No closing ']' found. Just consume the [ + if ( !res ) + return [ 1, "[" ]; + + var consumed = 1 + res[ 0 ], + children = res[ 1 ], + link, + attrs; + + // At this point the first [...] has been parsed. See what follows to find + // out which kind of link we are (reference or direct url) + text = text.substr( consumed ); + + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 <--- captures + // This will capture up to the last paren in the block. We then pull + // back based on if there a matching ones in the url + // ([here](/url/(test)) + // The parens have to be balanced + var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); + if ( m ) { + var url = m[1]; + consumed += m[0].length; + + if ( url && url[0] === "<" && url[url.length-1] === ">" ) + url = url.substring( 1, url.length - 1 ); + + // If there is a title we don't have to worry about parens in the url + if ( !m[3] ) { + var open_parens = 1; // One open that isn't in the capture + for ( var len = 0; len < url.length; len++ ) { + switch ( url[len] ) { + case "(": + open_parens++; + break; + case ")": + if ( --open_parens === 0) { + consumed -= url.length - len; + url = url.substring(0, len); + } + break; + } + } + } + + // Process escapes only + url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; + + attrs = { href: url || "" }; + if ( m[3] !== undefined) + attrs.title = m[3]; + + link = [ "link", attrs ].concat( children ); + return [ consumed, link ]; + } + + // [Alt text][id] + // [Alt text] [id] + m = text.match( /^\s*\[(.*?)\]/ ); + + if ( m ) { + + consumed += m[ 0 ].length; + + // [links][] uses links as its reference + attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; + + link = [ "link_ref", attrs ].concat( children ); + + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion. + // Store the original so that conversion can revert if the ref isn't found. + return [ consumed, link ]; + } + + // [id] + // Only if id is plain (no formatting.) + if ( children.length === 1 && typeof children[0] === "string" ) { + + attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; + link = [ "link_ref", attrs, children[0] ]; + return [ consumed, link ]; + } + + // Just consume the "[" + return [ 1, "[" ]; + }, + + + "<": function autoLink( text ) { + var m; + + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { + if ( m[3] ) + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; + else if ( m[2] === "mailto" ) + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; + else + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; + } + + return [ 1, "<" ]; + }, + + "`": function inlineCode( text ) { + // Inline code block. as many backticks as you like to start it + // Always skip over the opening ticks. + var m = text.match( /(`+)(([\s\S]*?)\1)/ ); + + if ( m && m[2] ) + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; + else { + // TODO: No matching end code found - warn! + return [ 1, "`" ]; + } + }, + + " \n": function lineBreak() { + return [ 3, [ "linebreak" ] ]; + } + + } + }; + + // Meta Helper/generator method for em and strong handling + function strong_em( tag, md ) { + + var state_slot = tag + "_state", + other_slot = tag === "strong" ? "em_state" : "strong_state"; + + function CloseTag(len) { + this.len_after = len; + this.name = "close_" + md; + } + + return function ( text ) { + + if ( this[state_slot][0] === md ) { + // Most recent em is of this type + //D:this.debug("closing", md); + this[state_slot].shift(); + + // "Consume" everything to go back to the recrusion in the else-block below + return[ text.length, new CloseTag(text.length-md.length) ]; + } + else { + // Store a clone of the em/strong states + var other = this[other_slot].slice(), + state = this[state_slot].slice(); + + this[state_slot].unshift(md); + + //D:this.debug_indent += " "; + + // Recurse + var res = this.processInline( text.substr( md.length ) ); + //D:this.debug_indent = this.debug_indent.substr(2); + + var last = res[res.length - 1]; + + //D:this.debug("processInline from", tag + ": ", uneval( res ) ); + + var check = this[state_slot].shift(); + if ( last instanceof CloseTag ) { + res.pop(); + // We matched! Huzzah. + var consumed = text.length - last.len_after; + return [ consumed, [ tag ].concat(res) ]; + } + else { + // Restore the state of the other kind. We might have mistakenly closed it. + this[other_slot] = other; + this[state_slot] = state; + + // We can't reuse the processed result as it could have wrong parsing contexts in it. + return [ md.length, md ]; + } + } + }; // End returned function + } + + Gruber.inline["**"] = strong_em("strong", "**"); + Gruber.inline["__"] = strong_em("strong", "__"); + Gruber.inline["*"] = strong_em("em", "*"); + Gruber.inline["_"] = strong_em("em", "_"); + + Markdown.dialects.Gruber = Gruber; + Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); + + + + var Maruku = DialectHelpers.subclassDialect( Gruber ), + extract_attr = MarkdownHelpers.extract_attr, + forEach = MarkdownHelpers.forEach; + + Maruku.processMetaHash = function processMetaHash( meta_string ) { + var meta = split_meta_hash( meta_string ), + attr = {}; + + for ( var i = 0; i < meta.length; ++i ) { + // id: #foo + if ( /^#/.test( meta[ i ] ) ) + attr.id = meta[ i ].substring( 1 ); + // class: .foo + else if ( /^\./.test( meta[ i ] ) ) { + // if class already exists, append the new one + if ( attr["class"] ) + attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); + else + attr["class"] = meta[ i ].substring( 1 ); + } + // attribute: foo=bar + else if ( /\=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /\=/ ); + attr[ s[ 0 ] ] = s[ 1 ]; + } + } + + return attr; + }; + + function split_meta_hash( meta_string ) { + var meta = meta_string.split( "" ), + parts = [ "" ], + in_quotes = false; + + while ( meta.length ) { + var letter = meta.shift(); + switch ( letter ) { + case " " : + // if we're in a quoted section, keep it + if ( in_quotes ) + parts[ parts.length - 1 ] += letter; + // otherwise make a new part + else + parts.push( "" ); + break; + case "'" : + case '"' : + // reverse the quotes and move straight on + in_quotes = !in_quotes; + break; + case "\\" : + // shift off the next letter to be used straight away. + // it was escaped so we'll keep it whatever it is + letter = meta.shift(); + /* falls through */ + default : + parts[ parts.length - 1 ] += letter; + break; + } + } + + return parts; + } + + Maruku.block.document_meta = function document_meta( block ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) + return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var pairs = block.split( /\n/ ); + for ( var p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; + }; + + Maruku.block.block_meta = function block_meta( block ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) + return undefined; + + // process the meta hash + var attr = this.dialect.processMetaHash( m[ 2 ] ), + hash; + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ]; + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) + return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + return result; + }; + + Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, + list = [ "dl" ], + i, m; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) + blocks.push( next.shift() ); + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( i = 0; i < terms.length; ++i ) + list.push( [ "dt", terms[ i ] ] ); + + for ( i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; + }; + + // splits on unescaped instances of @ch. If @ch is not a character the result + // can be unpredictable + + Maruku.block.table = function table ( block ) { + + var _split_on_unescaped = function( s, ch ) { + ch = ch || '\\s'; + if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) + ch = '\\' + ch; + var res = [ ], + r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), + m; + while ( ( m = s.match( r ) ) ) { + res.push( m[1] ); + s = m[2]; + } + res.push(s); + return res; + }; + + var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, + // find at least an unescaped pipe in each line + no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, + i, + m; + if ( ( m = block.match( leading_pipe ) ) ) { + // remove leading pipes in contents + // (header and horizontal rule already have the leading pipe left out) + m[3] = m[3].replace(/^\s*\|/gm, ''); + } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { + return undefined; + } + + var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; + + // remove trailing pipes, then split on pipes + // (no escaped pipes are allowed in horizontal rule) + m[2] = m[2].replace(/\|\s*$/, '').split('|'); + + // process alignment + var html_attrs = [ ]; + forEach (m[2], function (s) { + if (s.match(/^\s*-+:\s*$/)) + html_attrs.push({align: "right"}); + else if (s.match(/^\s*:-+\s*$/)) + html_attrs.push({align: "left"}); + else if (s.match(/^\s*:-+:\s*$/)) + html_attrs.push({align: "center"}); + else + html_attrs.push({}); + }); + + // now for the header, avoid escaped pipes + m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); + for (i = 0; i < m[1].length; i++) { + table[1][1].push(['th', html_attrs[i] || {}].concat( + this.processInline(m[1][i].trim()))); + } + + // now for body contents + forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { + var html_row = ['tr']; + row = _split_on_unescaped(row, '|'); + for (i = 0; i < row.length; i++) + html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); + table[2].push(html_row); + }, this); + + return [table]; + }; + + Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) + return [ 2, "{:" ]; + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) + return [ 2, "{:" ]; + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) + return [ 2, "{:" ]; + + // attach the attributes to the preceeding element + var meta = this.dialect.processMetaHash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) + attr[ k ] = meta[ k ]; + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; + }; + + + Markdown.dialects.Maruku = Maruku; + Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; + Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + + +// Include all our depndencies and; + expose.Markdown = Markdown; + expose.parse = Markdown.parse; + expose.toHTML = Markdown.toHTML; + expose.toHTMLTree = Markdown.toHTMLTree; + expose.renderJsonML = Markdown.renderJsonML; + +})(function() { + window.markdown = {}; + return window.markdown; +}()); diff --git a/demo/public/js/mustache.js b/demo/public/js/mustache.js new file mode 100644 index 0000000..18d92a5 --- /dev/null +++ b/demo/public/js/mustache.js @@ -0,0 +1,586 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (global, factory) { + if (typeof exports === "object" && exports) { + factory(exports); // CommonJS + } else if (typeof define === "function" && define.amd) { + define(['exports'], factory); // AMD + } else { + factory(global.Mustache = {}); // - + @@ -124,18 +124,18 @@ instance d'ici peu.

    - -
    Chargement...
    - - + @@ -124,18 +124,18 @@ instance d'ici peu.

    - -
    Chargement...
    - diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 1951e76..43068b0 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -38,7 +38,9 @@ function stacosys_load() { // Response handlers. xhr.onload = function() { var jsonResponse = JSON.parse(xhr.responseText); - console.log(jsonResponse); + for (var i = 0, numTokens = jsonResponse.data.length; i < numTokens; ++i) { + jsonResponse.data[i].mdcontent = markdown.toHTML(jsonResponse.data[i].content); + } var template = document.getElementById('stacosys-template').innerHTML; var rendered = Mustache.render(template, jsonResponse); document.getElementById('stacosys-comments').innerHTML = rendered; From ac00f6ad33ada626fac359b8ce1d1e6b027bc619 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 9 May 2015 16:28:33 +0200 Subject: [PATCH 034/586] Convert Markdown to HTML in JS --- demo/public/index.html | 2 +- demo/public/js/stacosys.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index 2528c68..81165bc 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -134,7 +134,7 @@ instance d'ici peu.

    {{author}} - {{date}} -

    {{content}}

    +{{{ mdcontent}}} {{/data}} diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 1951e76..43068b0 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -38,7 +38,9 @@ function stacosys_load() { // Response handlers. xhr.onload = function() { var jsonResponse = JSON.parse(xhr.responseText); - console.log(jsonResponse); + for (var i = 0, numTokens = jsonResponse.data.length; i < numTokens; ++i) { + jsonResponse.data[i].mdcontent = markdown.toHTML(jsonResponse.data[i].content); + } var template = document.getElementById('stacosys-template').innerHTML; var rendered = Mustache.render(template, jsonResponse); document.getElementById('stacosys-comments').innerHTML = rendered; From 71383eff9a5c65531521b02362397fdf582f8080 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 9 May 2015 20:25:35 +0200 Subject: [PATCH 035/586] Add 'get count of comment' method Draft JavaScript side --- app/controllers/api.py | 26 +++++++++---- demo/public/index.html | 78 +++++++++++++++++++++----------------- demo/public/js/page.js | 33 ++++++++++++++++ demo/public/js/stacosys.js | 38 ++++++++++++++----- 4 files changed, 124 insertions(+), 51 deletions(-) create mode 100644 demo/public/js/page.js diff --git a/app/controllers/api.py b/app/controllers/api.py index 50004b6..9069eff 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -11,17 +11,13 @@ from app.helpers.hashing import md5 logger = logging.getLogger(__name__) -@app.route("/comments", methods=['GET', 'POST']) +@app.route("/comments", methods=['GET']) def query_comments(): comments = [] try: - if request.method == 'POST': - token = request.json['token'] - url = request.json['url'] - else: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get('token', '') + url = request.args.get('url', '') logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( @@ -44,3 +40,19 @@ def query_comments(): r = jsonify({'data': []}) r.status_code = 400 return r + + +@app.route("/comments/count", methods=['GET']) +def get_comments_count(): + try: + token = request.args.get('token', '') + url = request.args.get('url', '') + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).count() + r = jsonify({'count': count}) + r.status_code = 200 + except: + r = jsonify({'count': 0}) + r.status_code = 200 + return r diff --git a/demo/public/index.html b/demo/public/index.html index 81165bc..9eee8dc 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -35,11 +35,11 @@ + - - + @@ -92,7 +92,7 @@ instance d'ici peu.

    + + + + +

    + + -
    Chargement...
    + + + -
    + @@ -203,6 +194,23 @@ function Editor(input, preview) { + + + + + + + + diff --git a/demo/public/js/page.js b/demo/public/js/page.js new file mode 100644 index 0000000..f3acbab --- /dev/null +++ b/demo/public/js/page.js @@ -0,0 +1,33 @@ +function show_hide(panel_id, button_id){ + if (document.getElementById(panel_id).style.display == 'none'){ + document.getElementById(panel_id).style.display = ''; + document.getElementById(button_id).style.display = 'none'; + } else { + document.getElementById(panel_id).style.display = 'none'; + } +} + +function show_comments() { + stacosys_load(); + show_hide('stacosys-comments', 'show-comments-button'); +} + +function preview_markdown() { + if (document.getElementById('preview-container').style.display == 'none'){ + document.getElementById('preview-container').style.display = ''; + } + var $ = function (id) { return document.getElementById(id); }; + new Editor($("message"), $("preview")); +} + +function Editor(input, preview) { + this.update = function () { + preview.innerHTML = markdown.toHTML(input.value); + }; + input.editor = this; + this.update(); +} + +function get_action() { + return '/post_a_new_comment'; +} diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 43068b0..24ea418 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,11 +1,6 @@ // Released under Apache license // Copyright (c) 2015 Yannic ARNOUX -STACOSYS_URL = 'http://127.0.0.1:8000'; -STACOSYS_TOKEN = '9fb3fc042c572cb831005fd16186126765140fa2bd9bb2d4a28e47a9457dc26c'; -//STACOSYS_PAGE = 'blogduyax.madyanne.fr/mes-applications-pour-blackberry.html' -STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' - // Create the XHR object. function stacosys_get_cors_request(method, url) { var xhr = new XMLHttpRequest(); @@ -23,12 +18,38 @@ function stacosys_get_cors_request(method, url) { return xhr; } -function stacosys_get_url() { - return STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; +function stacosys_count() { + var url = STACOSYS_URL + '/comments/count?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); + if (!xhr) { + console.log('CORS not supported'); + return 0; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + var count = jsonResponse.count; + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } + return jsonResponse.count; + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + return 0; + }; + + xhr.send(); } function stacosys_load() { - var url = stacosys_get_url(); + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); if (!xhr) { alert('CORS not supported'); @@ -52,4 +73,3 @@ function stacosys_load() { xhr.send(); } -window.onload = stacosys_load; From 20b01caf86a0fc30dc73290d4cfdc5eb623fb4c5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 9 May 2015 20:25:35 +0200 Subject: [PATCH 036/586] Add 'get count of comment' method Draft JavaScript side --- app/controllers/api.py | 26 +++++++++---- demo/public/index.html | 78 +++++++++++++++++++++----------------- demo/public/js/page.js | 33 ++++++++++++++++ demo/public/js/stacosys.js | 38 ++++++++++++++----- 4 files changed, 124 insertions(+), 51 deletions(-) create mode 100644 demo/public/js/page.js diff --git a/app/controllers/api.py b/app/controllers/api.py index 50004b6..9069eff 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -11,17 +11,13 @@ from app.helpers.hashing import md5 logger = logging.getLogger(__name__) -@app.route("/comments", methods=['GET', 'POST']) +@app.route("/comments", methods=['GET']) def query_comments(): comments = [] try: - if request.method == 'POST': - token = request.json['token'] - url = request.json['url'] - else: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get('token', '') + url = request.args.get('url', '') logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( @@ -44,3 +40,19 @@ def query_comments(): r = jsonify({'data': []}) r.status_code = 400 return r + + +@app.route("/comments/count", methods=['GET']) +def get_comments_count(): + try: + token = request.args.get('token', '') + url = request.args.get('url', '') + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Site.token == token)).count() + r = jsonify({'count': count}) + r.status_code = 200 + except: + r = jsonify({'count': 0}) + r.status_code = 200 + return r diff --git a/demo/public/index.html b/demo/public/index.html index 81165bc..9eee8dc 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -35,11 +35,11 @@ + - - + @@ -92,7 +92,7 @@ instance d'ici peu.

    + + + + +

    + + -
    Chargement...
    + + + -
    + @@ -203,6 +194,23 @@ function Editor(input, preview) { + + + + + + + + diff --git a/demo/public/js/page.js b/demo/public/js/page.js new file mode 100644 index 0000000..f3acbab --- /dev/null +++ b/demo/public/js/page.js @@ -0,0 +1,33 @@ +function show_hide(panel_id, button_id){ + if (document.getElementById(panel_id).style.display == 'none'){ + document.getElementById(panel_id).style.display = ''; + document.getElementById(button_id).style.display = 'none'; + } else { + document.getElementById(panel_id).style.display = 'none'; + } +} + +function show_comments() { + stacosys_load(); + show_hide('stacosys-comments', 'show-comments-button'); +} + +function preview_markdown() { + if (document.getElementById('preview-container').style.display == 'none'){ + document.getElementById('preview-container').style.display = ''; + } + var $ = function (id) { return document.getElementById(id); }; + new Editor($("message"), $("preview")); +} + +function Editor(input, preview) { + this.update = function () { + preview.innerHTML = markdown.toHTML(input.value); + }; + input.editor = this; + this.update(); +} + +function get_action() { + return '/post_a_new_comment'; +} diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 43068b0..24ea418 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,11 +1,6 @@ // Released under Apache license // Copyright (c) 2015 Yannic ARNOUX -STACOSYS_URL = 'http://127.0.0.1:8000'; -STACOSYS_TOKEN = '9fb3fc042c572cb831005fd16186126765140fa2bd9bb2d4a28e47a9457dc26c'; -//STACOSYS_PAGE = 'blogduyax.madyanne.fr/mes-applications-pour-blackberry.html' -STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' - // Create the XHR object. function stacosys_get_cors_request(method, url) { var xhr = new XMLHttpRequest(); @@ -23,12 +18,38 @@ function stacosys_get_cors_request(method, url) { return xhr; } -function stacosys_get_url() { - return STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; +function stacosys_count() { + var url = STACOSYS_URL + '/comments/count?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); + if (!xhr) { + console.log('CORS not supported'); + return 0; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + var count = jsonResponse.count; + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } + return jsonResponse.count; + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + return 0; + }; + + xhr.send(); } function stacosys_load() { - var url = stacosys_get_url(); + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); if (!xhr) { alert('CORS not supported'); @@ -52,4 +73,3 @@ function stacosys_load() { xhr.send(); } -window.onload = stacosys_load; From 37920f6c97cfd7ebc7ba592461ea39d1fbeb57aa Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 9 May 2015 20:46:48 +0200 Subject: [PATCH 037/586] Split page rendering and CORS requests against stacosys --- demo/public/index.html | 2 +- demo/public/js/page.js | 24 +++++++++++++- demo/public/js/stacosys.js | 66 ++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index 9eee8dc..52eb9c4 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -205,7 +205,7 @@ STACOSYS_TOKEN = '9fb3fc042c572cb831005fd16186126765140fa2bd9bb2d4a28e47a9457dc2 //STACOSYS_PAGE = 'blogduyax.madyanne.fr/mes-applications-pour-blackberry.html' STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' -window.onload = stacosys_count(); +window.onload = initialize_comments(); --> diff --git a/demo/public/js/page.js b/demo/public/js/page.js index f3acbab..adea0bf 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -8,8 +8,30 @@ function show_hide(panel_id, button_id){ } function show_comments() { - stacosys_load(); + stacosys_load(comments_loaded); +} + +function comments_loaded(response) { + for (var i = 0, numTokens = response.data.length; i < numTokens; ++i) { + response.data[i].mdcontent = markdown.toHTML(response.data[i].content); + } show_hide('stacosys-comments', 'show-comments-button'); + var template = document.getElementById('stacosys-template').innerHTML; + var rendered = Mustache.render(template, response); + document.getElementById('stacosys-comments').innerHTML = rendered; +} + +function initialize_comments() { + stacosys_count(comments_initialized); +} + +function comments_initialized(count) { + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } } function preview_markdown() { diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 24ea418..d8cd38b 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,4 +1,3 @@ -// Released under Apache license // Copyright (c) 2015 Yannic ARNOUX // Create the XHR object. @@ -18,53 +17,44 @@ function stacosys_get_cors_request(method, url) { return xhr; } -function stacosys_count() { +function stacosys_count(callback) { + var url = STACOSYS_URL + '/comments/count?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; var xhr = stacosys_get_cors_request('GET', url); if (!xhr) { console.log('CORS not supported'); - return 0; - } - - // Response handlers. - xhr.onload = function() { - var jsonResponse = JSON.parse(xhr.responseText); - var count = jsonResponse.count; - if (count > 0) { - if (count > 1) { - document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; - } - document.getElementById('show-comments-button').style.display = ''; - } - return jsonResponse.count; - }; - - xhr.onerror = function() { - console.log('Woops, there was an error making the request.'); - return 0; - }; - - xhr.send(); -} - -function stacosys_load() { - var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; - - var xhr = stacosys_get_cors_request('GET', url); - if (!xhr) { - alert('CORS not supported'); + callback(0); return; } // Response handlers. xhr.onload = function() { var jsonResponse = JSON.parse(xhr.responseText); - for (var i = 0, numTokens = jsonResponse.data.length; i < numTokens; ++i) { - jsonResponse.data[i].mdcontent = markdown.toHTML(jsonResponse.data[i].content); - } - var template = document.getElementById('stacosys-template').innerHTML; - var rendered = Mustache.render(template, jsonResponse); - document.getElementById('stacosys-comments').innerHTML = rendered; + var count = jsonResponse.count; + callback(count); + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + callback(0); + }; + + xhr.send(); +} + +function stacosys_load(callback) { + + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); + if (!xhr) { + console.log('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + callback(jsonResponse); }; xhr.onerror = function() { From 0f57aa267cdc1af3ed328cea09c4c2bed34c0fc0 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 9 May 2015 20:46:48 +0200 Subject: [PATCH 038/586] Split page rendering and CORS requests against stacosys --- demo/public/index.html | 2 +- demo/public/js/page.js | 24 +++++++++++++- demo/public/js/stacosys.js | 66 ++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index 9eee8dc..52eb9c4 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -205,7 +205,7 @@ STACOSYS_TOKEN = '9fb3fc042c572cb831005fd16186126765140fa2bd9bb2d4a28e47a9457dc2 //STACOSYS_PAGE = 'blogduyax.madyanne.fr/mes-applications-pour-blackberry.html' STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' -window.onload = stacosys_count(); +window.onload = initialize_comments(); --> diff --git a/demo/public/js/page.js b/demo/public/js/page.js index f3acbab..adea0bf 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -8,8 +8,30 @@ function show_hide(panel_id, button_id){ } function show_comments() { - stacosys_load(); + stacosys_load(comments_loaded); +} + +function comments_loaded(response) { + for (var i = 0, numTokens = response.data.length; i < numTokens; ++i) { + response.data[i].mdcontent = markdown.toHTML(response.data[i].content); + } show_hide('stacosys-comments', 'show-comments-button'); + var template = document.getElementById('stacosys-template').innerHTML; + var rendered = Mustache.render(template, response); + document.getElementById('stacosys-comments').innerHTML = rendered; +} + +function initialize_comments() { + stacosys_count(comments_initialized); +} + +function comments_initialized(count) { + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } } function preview_markdown() { diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 24ea418..d8cd38b 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,4 +1,3 @@ -// Released under Apache license // Copyright (c) 2015 Yannic ARNOUX // Create the XHR object. @@ -18,53 +17,44 @@ function stacosys_get_cors_request(method, url) { return xhr; } -function stacosys_count() { +function stacosys_count(callback) { + var url = STACOSYS_URL + '/comments/count?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; var xhr = stacosys_get_cors_request('GET', url); if (!xhr) { console.log('CORS not supported'); - return 0; - } - - // Response handlers. - xhr.onload = function() { - var jsonResponse = JSON.parse(xhr.responseText); - var count = jsonResponse.count; - if (count > 0) { - if (count > 1) { - document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; - } - document.getElementById('show-comments-button').style.display = ''; - } - return jsonResponse.count; - }; - - xhr.onerror = function() { - console.log('Woops, there was an error making the request.'); - return 0; - }; - - xhr.send(); -} - -function stacosys_load() { - var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; - - var xhr = stacosys_get_cors_request('GET', url); - if (!xhr) { - alert('CORS not supported'); + callback(0); return; } // Response handlers. xhr.onload = function() { var jsonResponse = JSON.parse(xhr.responseText); - for (var i = 0, numTokens = jsonResponse.data.length; i < numTokens; ++i) { - jsonResponse.data[i].mdcontent = markdown.toHTML(jsonResponse.data[i].content); - } - var template = document.getElementById('stacosys-template').innerHTML; - var rendered = Mustache.render(template, jsonResponse); - document.getElementById('stacosys-comments').innerHTML = rendered; + var count = jsonResponse.count; + callback(count); + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + callback(0); + }; + + xhr.send(); +} + +function stacosys_load(callback) { + + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + '&url=' + STACOSYS_PAGE; + var xhr = stacosys_get_cors_request('GET', url); + if (!xhr) { + console.log('CORS not supported'); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + callback(jsonResponse); }; xhr.onerror = function() { From 504e0347bf165e0f1d13c9751ac6669684e5844d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 10 May 2015 12:44:04 +0200 Subject: [PATCH 039/586] Draft new comment submitting --- app/controllers/api.py | 50 +++++++++++++++++++++++++++++++++++++++++- demo/public/index.html | 9 ++++---- demo/public/js/page.js | 47 ++++++++++++++++++++++++++++----------- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 9069eff..ca2922d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify +from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment @@ -56,3 +56,51 @@ def get_comments_count(): r = jsonify({'count': 0}) r.status_code = 200 return r + + +@app.route("/comments", methods=['POST']) +def new_comment(): + + logger.info("new comment !!!!") + + try: + token = request.form['token'] + site = Site.select().where(Site.token == token).get() + + # FOR DEBUG + return "OK" + + source_url = request.headers.get('referer', '') + url = app.config["pecosys"]["post"]["redirect_url"] + + if app.config["pecosys"]["post"]["redirect_referer"]: + url = app.config["pecosys"]["post"]["redirect_url"] + '?referer=' + request.headers.get('referer', '') + else: + url = request.headers.get('referer', app.config["pecosys"]["post"]["redirect_url"]) + + # get form values and create comment file + author = request.form['author'] + email = request.form['email'] + site = request.form['site'] + article = request.form['article'] + message = request.form['message'] + subscribe = False + if "subscribe" in request.form and request.form['subscribe'] == "on": + subscribe = True + # honeypot for spammers + captcha = "" + if "captcha" in request.form: + captcha = request.form['captcha'] + if captcha: + logger.warn("discard spam: captcha %s author %s email %s site %s article %s message %s" + % (captcha, author, email, site, article, message)) + else: + req = {'type': 'comment', 'author': author, 'email': email, 'site': site, 'article': article, + 'message': message, 'url': source_url, 'subscribe': subscribe} + processor.enqueue(req) + + except: + logger.exception("new comment failure") + abort(400) + + return "OK" diff --git a/demo/public/index.html b/demo/public/index.html index 52eb9c4..3abe8ae 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -90,13 +90,14 @@ en vaut la peine. Je l'utilise dans le cadre du projet Framabag mais je prévois d'installer ma propre instance d'ici peu.

    -
    + +
    - - + +
    diff --git a/demo/public/js/page.js b/demo/public/js/page.js index adea0bf..a1b176e 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -1,3 +1,7 @@ +// -------------------------------------------------------------------------- +// Common functions +// -------------------------------------------------------------------------- + function show_hide(panel_id, button_id){ if (document.getElementById(panel_id).style.display == 'none'){ document.getElementById(panel_id).style.display = ''; @@ -7,6 +11,23 @@ function show_hide(panel_id, button_id){ } } +// -------------------------------------------------------------------------- +// Load and display page comments +// -------------------------------------------------------------------------- + +function initialize_comments() { + stacosys_count(comments_initialized); +} + +function comments_initialized(count) { + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } +} + function show_comments() { stacosys_load(comments_loaded); } @@ -21,18 +42,20 @@ function comments_loaded(response) { document.getElementById('stacosys-comments').innerHTML = rendered; } -function initialize_comments() { - stacosys_count(comments_initialized); +// -------------------------------------------------------------------------- +// Submit a new comment +// -------------------------------------------------------------------------- + +function new_comment() { + var author = document.getElementById('author').value; + // TODO make CORS POST request + // and asynchronously redirect depending on result + console.log('SUBMIT ' + author); } -function comments_initialized(count) { - if (count > 0) { - if (count > 1) { - document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; - } - document.getElementById('show-comments-button').style.display = ''; - } -} +// -------------------------------------------------------------------------- +// Markdown preview +// -------------------------------------------------------------------------- function preview_markdown() { if (document.getElementById('preview-container').style.display == 'none'){ @@ -50,6 +73,4 @@ function Editor(input, preview) { this.update(); } -function get_action() { - return '/post_a_new_comment'; -} + From deabd382be659328e64b281dda0cb5f6f7b4aacb Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 10 May 2015 12:44:04 +0200 Subject: [PATCH 040/586] Draft new comment submitting --- app/controllers/api.py | 50 +++++++++++++++++++++++++++++++++++++++++- demo/public/index.html | 9 ++++---- demo/public/js/page.js | 47 ++++++++++++++++++++++++++++----------- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 9069eff..ca2922d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify +from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment @@ -56,3 +56,51 @@ def get_comments_count(): r = jsonify({'count': 0}) r.status_code = 200 return r + + +@app.route("/comments", methods=['POST']) +def new_comment(): + + logger.info("new comment !!!!") + + try: + token = request.form['token'] + site = Site.select().where(Site.token == token).get() + + # FOR DEBUG + return "OK" + + source_url = request.headers.get('referer', '') + url = app.config["pecosys"]["post"]["redirect_url"] + + if app.config["pecosys"]["post"]["redirect_referer"]: + url = app.config["pecosys"]["post"]["redirect_url"] + '?referer=' + request.headers.get('referer', '') + else: + url = request.headers.get('referer', app.config["pecosys"]["post"]["redirect_url"]) + + # get form values and create comment file + author = request.form['author'] + email = request.form['email'] + site = request.form['site'] + article = request.form['article'] + message = request.form['message'] + subscribe = False + if "subscribe" in request.form and request.form['subscribe'] == "on": + subscribe = True + # honeypot for spammers + captcha = "" + if "captcha" in request.form: + captcha = request.form['captcha'] + if captcha: + logger.warn("discard spam: captcha %s author %s email %s site %s article %s message %s" + % (captcha, author, email, site, article, message)) + else: + req = {'type': 'comment', 'author': author, 'email': email, 'site': site, 'article': article, + 'message': message, 'url': source_url, 'subscribe': subscribe} + processor.enqueue(req) + + except: + logger.exception("new comment failure") + abort(400) + + return "OK" diff --git a/demo/public/index.html b/demo/public/index.html index 52eb9c4..3abe8ae 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -90,13 +90,14 @@ en vaut la peine. Je l'utilise dans le cadre du projet Framabag mais je prévois d'installer ma propre instance d'ici peu.

    -
    + +
    - - + +
    diff --git a/demo/public/js/page.js b/demo/public/js/page.js index adea0bf..a1b176e 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -1,3 +1,7 @@ +// -------------------------------------------------------------------------- +// Common functions +// -------------------------------------------------------------------------- + function show_hide(panel_id, button_id){ if (document.getElementById(panel_id).style.display == 'none'){ document.getElementById(panel_id).style.display = ''; @@ -7,6 +11,23 @@ function show_hide(panel_id, button_id){ } } +// -------------------------------------------------------------------------- +// Load and display page comments +// -------------------------------------------------------------------------- + +function initialize_comments() { + stacosys_count(comments_initialized); +} + +function comments_initialized(count) { + if (count > 0) { + if (count > 1) { + document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; + } + document.getElementById('show-comments-button').style.display = ''; + } +} + function show_comments() { stacosys_load(comments_loaded); } @@ -21,18 +42,20 @@ function comments_loaded(response) { document.getElementById('stacosys-comments').innerHTML = rendered; } -function initialize_comments() { - stacosys_count(comments_initialized); +// -------------------------------------------------------------------------- +// Submit a new comment +// -------------------------------------------------------------------------- + +function new_comment() { + var author = document.getElementById('author').value; + // TODO make CORS POST request + // and asynchronously redirect depending on result + console.log('SUBMIT ' + author); } -function comments_initialized(count) { - if (count > 0) { - if (count > 1) { - document.getElementById('show-comment-label').innerHTML = 'Voir les ' + count + ' commentaires'; - } - document.getElementById('show-comments-button').style.display = ''; - } -} +// -------------------------------------------------------------------------- +// Markdown preview +// -------------------------------------------------------------------------- function preview_markdown() { if (document.getElementById('preview-container').style.display == 'none'){ @@ -50,6 +73,4 @@ function Editor(input, preview) { this.update(); } -function get_action() { - return '/post_a_new_comment'; -} + From a200fdfa88737b6e759cdeabe3fea8503376766b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 10 May 2015 19:39:32 +0200 Subject: [PATCH 041/586] Submitting comment. Work in progress --- demo/public/index.html | 1 - demo/public/js/page.js | 13 +++++-- demo/public/js/stacosys.js | 72 +++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index 3abe8ae..41c4db0 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -117,7 +117,6 @@ instance d'ici peu.

    (Les champs obligatoires sont notés avec une *)

    diff --git a/demo/public/js/page.js b/demo/public/js/page.js index a1b176e..d027626 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -48,9 +48,16 @@ function comments_loaded(response) { function new_comment() { var author = document.getElementById('author').value; - // TODO make CORS POST request - // and asynchronously redirect depending on result - console.log('SUBMIT ' + author); + var email = document.getElementById('email').value; + var site = document.getElementById('site').value; + var captcha = document.getElementById('captcha').value; + //var subscribe = document.getElementById('subscribe').value; + + stacosys_new(author, email, site, captcha, comment_submitted); +} + +function comment_submitted(success) { + console.log('SUBMITTED : ' + success); } // -------------------------------------------------------------------------- diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index d8cd38b..ef1405c 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,5 +1,48 @@ // Copyright (c) 2015 Yannic ARNOUX +/** + * Make a X-Domain request to url and callback. + * + * @param url {String} + * @param method {String} HTTP verb ('GET', 'POST', 'DELETE', etc.) + * @param data {String} request body + * @param callback {Function} to callback on completion + * @param errback {Function} to callback on error + */ +function xdr(url, method, data, callback, errback) { + var req; + + if(XMLHttpRequest) { + req = new XMLHttpRequest(); + + if('withCredentials' in req) { + req.open(method, url, true); + req.onerror = errback; + req.onreadystatechange = function() { + if (req.readyState === 4) { + if (req.status >= 200 && req.status < 400) { + callback(req.responseText); + } else { + errback(new Error('Response returned with non-OK status')); + } + } + }; + req.send(data); + } + } else if(XDomainRequest) { + req = new XDomainRequest(); + req.open(method, url); + req.onerror = errback; + req.onload = function() { + callback(req.responseText); + }; + req.send(data); + } else { + errback(new Error('CORS not supported')); + } +} + + // Create the XHR object. function stacosys_get_cors_request(method, url) { var xhr = new XMLHttpRequest(); @@ -58,7 +101,34 @@ function stacosys_load(callback) { }; xhr.onerror = function() { - alert('Woops, there was an error making the request.'); + console.log('Woops, there was an error making the request.'); + }; + + xhr.send(); +} + +function stacosys_new(author, email, site, captcha, callback) { + + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + + '&url=' + STACOSYS_PAGE + '&author=' + author + + '&email=' + email + '&site=' + site + + '&captcha=' + captcha; + var xhr = stacosys_get_cors_request('POST', url); + if (!xhr) { + console.log('CORS not supported'); + callback(false); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + callback(jsonResponse); + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + callback(false); }; xhr.send(); From 538659c63455ca333fe7e1a8d6b8da761d872c8a Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 10 May 2015 19:39:32 +0200 Subject: [PATCH 042/586] Submitting comment. Work in progress --- demo/public/index.html | 1 - demo/public/js/page.js | 13 +++++-- demo/public/js/stacosys.js | 72 +++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index 3abe8ae..41c4db0 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -117,7 +117,6 @@ instance d'ici peu.

    (Les champs obligatoires sont notés avec une *)

    diff --git a/demo/public/js/page.js b/demo/public/js/page.js index a1b176e..d027626 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -48,9 +48,16 @@ function comments_loaded(response) { function new_comment() { var author = document.getElementById('author').value; - // TODO make CORS POST request - // and asynchronously redirect depending on result - console.log('SUBMIT ' + author); + var email = document.getElementById('email').value; + var site = document.getElementById('site').value; + var captcha = document.getElementById('captcha').value; + //var subscribe = document.getElementById('subscribe').value; + + stacosys_new(author, email, site, captcha, comment_submitted); +} + +function comment_submitted(success) { + console.log('SUBMITTED : ' + success); } // -------------------------------------------------------------------------- diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index d8cd38b..ef1405c 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -1,5 +1,48 @@ // Copyright (c) 2015 Yannic ARNOUX +/** + * Make a X-Domain request to url and callback. + * + * @param url {String} + * @param method {String} HTTP verb ('GET', 'POST', 'DELETE', etc.) + * @param data {String} request body + * @param callback {Function} to callback on completion + * @param errback {Function} to callback on error + */ +function xdr(url, method, data, callback, errback) { + var req; + + if(XMLHttpRequest) { + req = new XMLHttpRequest(); + + if('withCredentials' in req) { + req.open(method, url, true); + req.onerror = errback; + req.onreadystatechange = function() { + if (req.readyState === 4) { + if (req.status >= 200 && req.status < 400) { + callback(req.responseText); + } else { + errback(new Error('Response returned with non-OK status')); + } + } + }; + req.send(data); + } + } else if(XDomainRequest) { + req = new XDomainRequest(); + req.open(method, url); + req.onerror = errback; + req.onload = function() { + callback(req.responseText); + }; + req.send(data); + } else { + errback(new Error('CORS not supported')); + } +} + + // Create the XHR object. function stacosys_get_cors_request(method, url) { var xhr = new XMLHttpRequest(); @@ -58,7 +101,34 @@ function stacosys_load(callback) { }; xhr.onerror = function() { - alert('Woops, there was an error making the request.'); + console.log('Woops, there was an error making the request.'); + }; + + xhr.send(); +} + +function stacosys_new(author, email, site, captcha, callback) { + + var url = STACOSYS_URL + '/comments?token=' + STACOSYS_TOKEN + + '&url=' + STACOSYS_PAGE + '&author=' + author + + '&email=' + email + '&site=' + site + + '&captcha=' + captcha; + var xhr = stacosys_get_cors_request('POST', url); + if (!xhr) { + console.log('CORS not supported'); + callback(false); + return; + } + + // Response handlers. + xhr.onload = function() { + var jsonResponse = JSON.parse(xhr.responseText); + callback(jsonResponse); + }; + + xhr.onerror = function() { + console.log('Woops, there was an error making the request.'); + callback(false); }; xhr.send(); From 300727cdab1abf054593c5a91d5fca7ef78ced61 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 12 May 2015 22:30:10 +0200 Subject: [PATCH 043/586] Split cleanly stacosys API usage and page rendering Progress on new comment post method --- app/controllers/api.py | 52 ++++++++--------- demo/public/index.html | 6 +- demo/public/js/page.js | 34 ++++++++--- demo/public/js/stacosys.js | 112 +++++++++---------------------------- 4 files changed, 78 insertions(+), 126 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index ca2922d..c9362ab 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -61,43 +61,35 @@ def get_comments_count(): @app.route("/comments", methods=['POST']) def new_comment(): - logger.info("new comment !!!!") - try: - token = request.form['token'] + data = request.get_json() + logger.info(data) + + # validate token: retrieve site entity + token = data.get('token', '') site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(400) - # FOR DEBUG - return "OK" + # get values + url = data.get('url', '') + author_name = data.get('author', '') + author_email = data.get('email', '') + author_site = data.get('site', '') + message = data.get('message', '') + subscribe = data.get('subscribe', '') - source_url = request.headers.get('referer', '') - url = app.config["pecosys"]["post"]["redirect_url"] - - if app.config["pecosys"]["post"]["redirect_referer"]: - url = app.config["pecosys"]["post"]["redirect_url"] + '?referer=' + request.headers.get('referer', '') - else: - url = request.headers.get('referer', app.config["pecosys"]["post"]["redirect_url"]) - - # get form values and create comment file - author = request.form['author'] - email = request.form['email'] - site = request.form['site'] - article = request.form['article'] - message = request.form['message'] - subscribe = False - if "subscribe" in request.form and request.form['subscribe'] == "on": - subscribe = True # honeypot for spammers - captcha = "" - if "captcha" in request.form: - captcha = request.form['captcha'] + captcha = data.get('captcha', '') if captcha: - logger.warn("discard spam: captcha %s author %s email %s site %s article %s message %s" - % (captcha, author, email, site, article, message)) + logger.warn('discard spam: captcha %s author %s email %s site %s url %s message %s' + % (captcha, author_name, author_email, author_site, url, message)) else: - req = {'type': 'comment', 'author': author, 'email': email, 'site': site, 'article': article, - 'message': message, 'url': source_url, 'subscribe': subscribe} - processor.enqueue(req) + # TODO push new comment to backend service + logger.info('process: captcha %s author %s email %s site %s url %s message %s subscribe %s' + % (captcha, author_name, author_email, author_site, + url, message, subscribe)) except: logger.exception("new comment failure") diff --git a/demo/public/index.html b/demo/public/index.html index 41c4db0..ad7c883 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -200,10 +200,10 @@ instance d'ici peu.

    + + + diff --git a/requirements.txt b/requirements.txt index ce353a8..cd3fc9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +chardet==2.3.0 clize==2.4 Flask==0.10.1 Flask-Cors==2.0.1 diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index f68873b..408db43 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -35,7 +35,12 @@ logger.addHandler(ch) # regex regex = re.compile(r"(\w+):\s*(.*)") -def convert_comment(db, site, filename): +def remove_from_string(line, start): + if line[:len(start)] == start: + line = line[len(start):].strip() + return line + +def convert_comment(db, site, root_url, filename): logger.info('convert %s' % filename) d = {} content = '' @@ -62,10 +67,10 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'].strip() if 'url' in d: - if d['url'][:7] == 'http://': - comment.url = d['url'][7:].strip() - elif d['url'][:8] == 'https://': - comment.url = d['url'][8:].strip() + url = remove_from_string(d['url'], 'https://') + url = remove_from_string(url, 'http://') + url = remove_from_string(url, root_url) + comment.url = remove_from_string(url, '/') # else: # comment.url = d['article'] if 'date' in d: @@ -94,7 +99,7 @@ def convert(db, site_name, url, comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) - convert_comment(db, site, comment_file) + convert_comment(db, site, url, comment_file) else: logger.warn('ignore file %s' % filename) From a525c4de19db85e84b4679160bcd6a948766328a Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 15 May 2015 19:51:02 +0200 Subject: [PATCH 046/586] Merge pecosys work around comment processing with stacosys --- app/controllers/api.py | 22 +-- app/models/comment.py | 2 +- app/run.py | 13 +- app/services/processor.py | 231 +++++++++++++++++++++++++ app/templates/en/approve_comment.tpl | 9 + app/templates/en/drop_comment.tpl | 9 + app/templates/en/new_comment.tpl | 16 ++ app/templates/en/notify_message.tpl | 1 + app/templates/en/notify_reader.tpl | 9 + app/templates/en/notify_subscriber.tpl | 13 ++ app/templates/en/unsubscribe_page.tpl | 2 + app/templates/fr/approve_comment.tpl | 9 + app/templates/fr/drop_comment.tpl | 9 + app/templates/fr/new_comment.tpl | 16 ++ app/templates/fr/notify_message.tpl | 1 + app/templates/fr/notify_reader.tpl | 9 + app/templates/fr/notify_subscriber.tpl | 13 ++ app/templates/fr/unsubscribe_page.tpl | 2 + config.py | 2 + demo/public/index.html | 3 +- demo/public/js/page.js | 5 +- demo/public/js/stacosys.js | 6 +- demo/public/redirect.html | 67 +++++++ requirements.txt | 1 + tools/pecosys2stacosys.py | 17 +- 25 files changed, 456 insertions(+), 31 deletions(-) create mode 100644 app/services/processor.py create mode 100644 app/templates/en/approve_comment.tpl create mode 100644 app/templates/en/drop_comment.tpl create mode 100644 app/templates/en/new_comment.tpl create mode 100644 app/templates/en/notify_message.tpl create mode 100644 app/templates/en/notify_reader.tpl create mode 100644 app/templates/en/notify_subscriber.tpl create mode 100644 app/templates/en/unsubscribe_page.tpl create mode 100644 app/templates/fr/approve_comment.tpl create mode 100644 app/templates/fr/drop_comment.tpl create mode 100644 app/templates/fr/new_comment.tpl create mode 100644 app/templates/fr/notify_message.tpl create mode 100644 app/templates/fr/notify_reader.tpl create mode 100644 app/templates/fr/notify_subscriber.tpl create mode 100644 app/templates/fr/unsubscribe_page.tpl create mode 100644 demo/public/redirect.html diff --git a/app/controllers/api.py b/app/controllers/api.py index c9362ab..0c9a765 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -7,6 +7,7 @@ from app import app from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 +from app.services import processor logger = logging.getLogger(__name__) @@ -22,6 +23,7 @@ def query_comments(): logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( (Comment.url == url) & + (Comment.published.is_null(False)) & (Site.token == token)).order_by(+Comment.published): d = {} d['author'] = comment.author_name @@ -49,6 +51,7 @@ def get_comments_count(): url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( (Comment.url == url) & + (Comment.published.is_null(False)) & (Site.token == token)).count() r = jsonify({'count': count}) r.status_code = 200 @@ -72,24 +75,13 @@ def new_comment(): logger.warn('Unknown site %s' % token) abort(400) - # get values - url = data.get('url', '') - author_name = data.get('author', '') - author_email = data.get('email', '') - author_site = data.get('site', '') - message = data.get('message', '') - subscribe = data.get('subscribe', '') - # honeypot for spammers captcha = data.get('captcha', '') if captcha: - logger.warn('discard spam: captcha %s author %s email %s site %s url %s message %s' - % (captcha, author_name, author_email, author_site, url, message)) - else: - # TODO push new comment to backend service - logger.info('process: captcha %s author %s email %s site %s url %s message %s subscribe %s' - % (captcha, author_name, author_email, author_site, - url, message, subscribe)) + logger.warn('discard spam: data %s' % data) + abort(400) + + processor.enqueue({'request': 'new_comment', 'data': data}) except: logger.exception("new comment failure") diff --git a/app/models/comment.py b/app/models/comment.py index ab80933..1b79a58 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,7 +13,7 @@ from app.services.database import get_db class Comment(Model): url = CharField() created = DateTimeField() - published = DateTimeField() + published = DateTimeField(null=True,default=None) author_name = CharField() author_email = CharField(default='') author_site = CharField(default='') diff --git a/app/run.py b/app/run.py index 0a5e674..c513b6d 100644 --- a/app/run.py +++ b/app/run.py @@ -7,10 +7,10 @@ import logging from werkzeug.contrib.fixers import ProxyFix from flask.ext.cors import CORS -# add current and parent path to syspath -currentPath = os.path.dirname(__file__) -parentPath = os.path.abspath(os.path.join(currentPath, os.path.pardir)) -paths = [currentPath, parentPath] +# add current path and parent path to syspath +current_path = os.path.dirname(__file__) +parent_path = os.path.abspath(os.path.join(current_path, os.path.pardir)) +paths = [current_path, parent_path] for path in paths: if path not in sys.path: sys.path.insert(0, path) @@ -18,6 +18,7 @@ for path in paths: # more imports import config from app.services import database +from app.services import processor from app.controllers import api from app import app @@ -42,6 +43,10 @@ logger = logging.getLogger(__name__) # initialize database database.setup() +# start processor +template_path = os.path.abspath(os.path.join(current_path, 'templates')) +processor.start(template_path) + app.wsgi_app = ProxyFix(app.wsgi_app) logger.info("Start Stacosys application") diff --git a/app/services/processor.py b/app/services/processor.py new file mode 100644 index 0000000..5d0fbf9 --- /dev/null +++ b/app/services/processor.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import re +from datetime import datetime +from threading import Thread +from queue import Queue +import chardet +from jinja2 import Environment, FileSystemLoader +from app.models.site import Site +from app.models.comment import Comment + + +logger = logging.getLogger(__name__) +queue = Queue() +proc = None +env = None + + +class Processor(Thread): + + def stop(self): + logger.info("stop requested") + self.is_running = False + + def run(self): + + self.is_running = True + while self.is_running: + msg = queue.get() + if msg['request'] == 'new_comment': + new_comment(msg['data']) + #elif msg['type'] == 'reply_comment_email': + # reply_comment_email(req['From'], req['Subject'], req['Body']) + #elif req['type'] == 'unsubscribe': + # unsubscribe_reader(req['email'], req['article']) + else: + logger.info("Dequeue unknown request " + msg) + + +def new_comment(data): + + try: + token = data.get('token', '') + url = data.get('url', '') + author_name = data.get('author', '') + author_email = data.get('email', '') + author_site = data.get('site', '') + message = data.get('message', '') + subscribe = data.get('subscribe', '') + + # create a new comment row + site = Site.select().where(Site.token == token).get() + + logger.info('new comment received: %s' % data) + + if author_site and author_site[:4] != 'http': + author_site = 'http://' + author_site + + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + comment = Comment(site=site, url=url, author_name=author_name, + author_site=author_site, author_email=author_email, + content=message, created=created, published=None) + comment.save() + + 1 / 0 + # Render email body template + email_body = get_template('new_comment').render(url=url, comment=comment) + + # Send email + mail(pecosys.get_config('post', 'from_email'), + pecosys.get_config('post', 'to_email'), + '[' + branch_name + '-' + article + ']', email_body) + + # Reader subscribes to further comments + if subscribe and email: + subscribe_reader(email, article, url) + + logger.debug("new comment processed ") + except: + logger.exception("new_comment failure") + + +def reply_comment_email(from_email, subject, message): + try: + m = re.search('\[(\d+)\-(\w+)\]', subject) + branch_name = m.group(1) + article = m.group(2) + + message = decode_best_effort(message) + + # safe logic: no answer or unknown answer is a go for publishing + if message[:2].upper() == 'NO': + logger.info('discard comment: %s' % branch_name) + email_body = get_template('drop_comment').render(original=message) + mail(pecosys.get_config('post', 'from_email'), + pecosys.get_config('post', 'to_email'), + 'Re: ' + subject, email_body) + else: + if pecosys.get_config("git", "disabled"): + logger.debug("GIT usage disabled (debug mode)") + else: + git.merge(branch_name) + if pecosys.get_config("git", "remote"): + git.push() + logger.info('commit comment: %s' % branch_name) + + # send approval confirmation email to admin + email_body = get_template('approve_comment').render(original=message) + mail(pecosys.get_config('post', 'from_email'), + pecosys.get_config('post', 'to_email'), + 'Re: ' + subject, email_body) + + # notify reader once comment is published + reader_email, article_url = get_email_metadata(message) + if reader_email: + notify_reader(reader_email, article_url) + + # notify subscribers every time a new comment is published + notify_subscribers(article) + + if pecosys.get_config("git", "disabled"): + logger.debug("GIT usage disabled (debug mode)") + else: + git.branch("-D", branch_name) + except: + logger.exception("new email failure") + + +def get_email_metadata(message): + # retrieve metadata reader email and URL from email body sent by admin + email = "" + url = "" + m = re.search('email:\s(.+@.+\..+)', message) + if m: + email = m.group(1) + + m = re.search('url:\s(.+)', message) + if m: + url = m.group(1) + return (email, url) + + +def subscribe_reader(email, article, url): + logger.info("subscribe reader %s to %s (%s)" % (email, article, url)) + db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') + db.insert({'email': email, 'article': article, 'url': url}) + + +def unsubscribe_reader(email, article): + logger.info("unsubscribe reader %s from %s" % (email, article)) + db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') + db.remove((where('email') == email) & (where('article') == article)) + + +def notify_subscribers(article): + logger.info('notify subscribers for article %s' % article) + db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') + for item in db.search(where('article') == article): + logger.info(item) + to_email = item['email'] + logger.info("notify reader %s for article %s" % (to_email, article)) + unsubscribe_url = pecosys.get_config('subscription', 'url') + '?email=' + to_email + '&article=' + article + email_body = get_template('notify_subscriber').render(article_url=item['url'], + unsubscribe_url=unsubscribe_url) + subject = get_template('notify_message').render() + mail(pecosys.get_config('subscription', 'from_email'), to_email, subject, email_body) + + +def notify_reader(email, url): + logger.info('notify reader: email %s about URL %s' % (email, url)) + email_body = get_template('notify_reader').render(article_url=url) + subject = get_template('notify_message').render() + mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body) + + +def decode_best_effort(string): + info = chardet.detect(string) + if info['confidence'] < 0.5: + return string.decode('utf8', errors='replace') + else: + return string.decode(info['encoding'], errors='replace') + + +def mail(from_email, to_email, subject, *messages): + + # Create the container (outer) email message. + msg = MIMEMultipart() + msg['Subject'] = subject + msg['From'] = from_email + msg['To'] = to_email + msg.preamble = subject + + for message in messages: + part = MIMEText(message, 'plain') + msg.attach(part) + + s = smtplib.SMTP(pecosys.get_config('smtp', 'host'), + pecosys.get_config('smtp', 'port')) + if(pecosys.get_config('smtp', 'starttls')): + s.starttls() + s.login(pecosys.get_config('smtp', 'login'), + pecosys.get_config('smtp', 'password')) + s.sendmail(from_email, to_email, msg.as_string()) + s.quit() + + +def get_template(name): + return env.get_template(pecosys.get_config('global', 'lang') + '/' + name + '.tpl') + + +def enqueue(something): + queue.put(something) + + +def get_processor(): + return proc + + +def start(template_dir): + global proc, env + + # initialize Jinja 2 templating + logger.info("load templates from directory %s" % template_dir) + env = Environment(loader=FileSystemLoader(template_dir)) + + # start processor thread + proc = Processor() + proc.start() diff --git a/app/templates/en/approve_comment.tpl b/app/templates/en/approve_comment.tpl new file mode 100644 index 0000000..2daa5b2 --- /dev/null +++ b/app/templates/en/approve_comment.tpl @@ -0,0 +1,9 @@ +Hi, + +The comment should be published soon. It has been approved. + +-- +Pecosys + + +{{ original }} diff --git a/app/templates/en/drop_comment.tpl b/app/templates/en/drop_comment.tpl new file mode 100644 index 0000000..c3b0362 --- /dev/null +++ b/app/templates/en/drop_comment.tpl @@ -0,0 +1,9 @@ +Hi, + +The comment will not be published. It has been dropped. + +-- +Pecosys + + +{{ original }} diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl new file mode 100644 index 0000000..a07f7a3 --- /dev/null +++ b/app/templates/en/new_comment.tpl @@ -0,0 +1,16 @@ +Hi, + +A new comment has been submitted for post {{ url }}. + +You have two choices: +- reject the comment by replying NO (or no), +- accept the comment by sending back the email as it is. + +If you choose the latter option, Pecosys is going to publish the commennt. + +Please find comment details below: + +{{ comment }} + +-- +Pecosys diff --git a/app/templates/en/notify_message.tpl b/app/templates/en/notify_message.tpl new file mode 100644 index 0000000..94a261f --- /dev/null +++ b/app/templates/en/notify_message.tpl @@ -0,0 +1 @@ +New comment diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl new file mode 100644 index 0000000..c2d2a0e --- /dev/null +++ b/app/templates/en/notify_reader.tpl @@ -0,0 +1,9 @@ +Hi, + +Your comment has been approved. It should be published in few minutes. + + {{ article_url }} + +-- +Pecosys + diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl new file mode 100644 index 0000000..d1d9e6c --- /dev/null +++ b/app/templates/en/notify_subscriber.tpl @@ -0,0 +1,13 @@ +Hi, + +A new comment has been published for an article you have subscribed to. + + {{ article_url }} + +You can unsubscribe at any time using this link: + + {{ unsubscribe_url }} + +-- +Pecosys + diff --git a/app/templates/en/unsubscribe_page.tpl b/app/templates/en/unsubscribe_page.tpl new file mode 100644 index 0000000..a52afd7 --- /dev/null +++ b/app/templates/en/unsubscribe_page.tpl @@ -0,0 +1,2 @@ +Your request has been sent. In case of issue please contact site +administrator. diff --git a/app/templates/fr/approve_comment.tpl b/app/templates/fr/approve_comment.tpl new file mode 100644 index 0000000..a381c21 --- /dev/null +++ b/app/templates/fr/approve_comment.tpl @@ -0,0 +1,9 @@ +Bonjour, + +Le commentaire sera bientôt publié. Il a été approuvé. + +-- +Pecosys + + +{{ original }} diff --git a/app/templates/fr/drop_comment.tpl b/app/templates/fr/drop_comment.tpl new file mode 100644 index 0000000..714decf --- /dev/null +++ b/app/templates/fr/drop_comment.tpl @@ -0,0 +1,9 @@ +Bonjour, + +Le commentaire ne sera pas publié. Il a été rejeté. + +-- +Pecosys + + +{{ original }} diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl new file mode 100644 index 0000000..b3346fe --- /dev/null +++ b/app/templates/fr/new_comment.tpl @@ -0,0 +1,16 @@ +Bonjour, + +Un nouveau commentaire a été posté pour l'article {{ url }}. + +Vous avez deux réponses possibles : +- rejeter le commentaire en répondant NO (ou no), +- accepter le commentaire en renvoyant cet email tel quel. + +Si cette dernière option est choisie, Pecosys publiera le commentaire très bientôt. + +Voici les détails concernant le commentaire : + +{{ comment }} + +-- +Pecosys diff --git a/app/templates/fr/notify_message.tpl b/app/templates/fr/notify_message.tpl new file mode 100644 index 0000000..5455f77 --- /dev/null +++ b/app/templates/fr/notify_message.tpl @@ -0,0 +1 @@ +Nouveau commentaire diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl new file mode 100644 index 0000000..c8d2956 --- /dev/null +++ b/app/templates/fr/notify_reader.tpl @@ -0,0 +1,9 @@ +Bonjour, + +Votre commentaire a été approuvé. Il sera publié dans quelques minutes. + + {{ article_url }} + +-- +Pecosys + diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl new file mode 100644 index 0000000..29804ee --- /dev/null +++ b/app/templates/fr/notify_subscriber.tpl @@ -0,0 +1,13 @@ +Bonjour, + +Un nouveau commentaire a été publié pour un article auquel vous êtes abonné. + + {{ article_url }} + +Vous pouvez vous désinscrire à tout moment en suivant ce lien : + + {{ unsubscribe_url }} + +-- +Pecosys + diff --git a/app/templates/fr/unsubscribe_page.tpl b/app/templates/fr/unsubscribe_page.tpl new file mode 100644 index 0000000..3cd63e8 --- /dev/null +++ b/app/templates/fr/unsubscribe_page.tpl @@ -0,0 +1,2 @@ +Votre requête a été envoyée. En cas de problème, contactez l'administrateur du +site. diff --git a/config.py b/config.py index 9777272..8fef52b 100644 --- a/config.py +++ b/config.py @@ -2,6 +2,8 @@ DEBUG = True +LANG = "en" + #DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" diff --git a/demo/public/index.html b/demo/public/index.html index ad7c883..7e016ef 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -203,7 +203,8 @@ instance d'ici peu.

    var STACOSYS_URL = 'http://127.0.0.1:8000'; var STACOSYS_TOKEN = '9fb3fc042c572cb831005fd16186126765140fa2bd9bb2d4a28e47a9457dc26c'; //STACOSYS_PAGE = 'blogduyax.madyanne.fr/mes-applications-pour-blackberry.html' -var STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' +//var STACOSYS_PAGE = 'blogduyax.madyanne.fr/migration-du-blog-sous-pelican.html' +var STACOSYS_PAGE = 'migration-du-blog-sous-pelican.html' window.onload = initialize_comments(); diff --git a/demo/public/js/page.js b/demo/public/js/page.js index 77c4813..06d6a97 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -65,9 +65,10 @@ function new_comment() { var email = document.getElementById('email').value; var site = document.getElementById('site').value; var captcha = document.getElementById('captcha').value; - //var subscribe = document.getElementById('subscribe').value; + var subscribe = document.getElementById('subscribe').value; + var message = document.getElementById('message').value; - stacosys_new_comment(author, email, site, captcha, submit_success, submit_failure); + stacosys_new_comment(author, email, site, captcha, subscribe, message, submit_success, submit_failure); } function submit_success(data) { diff --git a/demo/public/js/stacosys.js b/demo/public/js/stacosys.js index 8efba1a..0277443 100644 --- a/demo/public/js/stacosys.js +++ b/demo/public/js/stacosys.js @@ -57,7 +57,7 @@ function stacosys_load_comments(callback, errback) { xdr(url, 'GET', null, {}, callback, errback); } -function stacosys_new_comment(author, email, site, captcha, callback, errback) { +function stacosys_new_comment(author, email, site, captcha, subscribe, message, callback, errback) { var url = STACOSYS_URL + '/comments'; var data = { 'token': STACOSYS_TOKEN, @@ -65,7 +65,9 @@ function stacosys_new_comment(author, email, site, captcha, callback, errback) { 'author': author, 'email': email, 'site': site, - 'captcha': captcha + 'captcha': captcha, + 'subscribe': subscribe, + 'message': message }; var header = { 'Content-type': 'application/json' diff --git a/demo/public/redirect.html b/demo/public/redirect.html new file mode 100644 index 0000000..c7dc481 --- /dev/null +++ b/demo/public/redirect.html @@ -0,0 +1,67 @@ + + + + + + + + Le blog du Yax + + + + + + + + + + + + + + + + + +
    + +
    +

    + +
    +
    +
    +

    Le commentaire a été envoyé à l'administrateur du site.

    + Cliquez sur ce lien pour retourner à l'article. +
    +
    +
    + + + + + diff --git a/requirements.txt b/requirements.txt index ce353a8..cd3fc9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +chardet==2.3.0 clize==2.4 Flask==0.10.1 Flask-Cors==2.0.1 diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index f68873b..408db43 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -35,7 +35,12 @@ logger.addHandler(ch) # regex regex = re.compile(r"(\w+):\s*(.*)") -def convert_comment(db, site, filename): +def remove_from_string(line, start): + if line[:len(start)] == start: + line = line[len(start):].strip() + return line + +def convert_comment(db, site, root_url, filename): logger.info('convert %s' % filename) d = {} content = '' @@ -62,10 +67,10 @@ def convert_comment(db, site, filename): if 'site' in d: comment.author_site = d['site'].strip() if 'url' in d: - if d['url'][:7] == 'http://': - comment.url = d['url'][7:].strip() - elif d['url'][:8] == 'https://': - comment.url = d['url'][8:].strip() + url = remove_from_string(d['url'], 'https://') + url = remove_from_string(url, 'http://') + url = remove_from_string(url, root_url) + comment.url = remove_from_string(url, '/') # else: # comment.url = d['article'] if 'date' in d: @@ -94,7 +99,7 @@ def convert(db, site_name, url, comment_dir): for filename in files: if filename.endswith(('.md',)): comment_file = '/'.join([dirpath, filename]) - convert_comment(db, site, comment_file) + convert_comment(db, site, url, comment_file) else: logger.warn('ignore file %s' % filename) From 692b90d7d98387407ced8f7ffd514bc2bd92480a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 16 May 2015 10:59:44 +0200 Subject: [PATCH 047/586] Connect Stacosys to SRMail --- app/controllers/mail.py | 25 +++++++++++++++++++++++++ app/run.py | 1 + app/services/processor.py | 2 +- config.py | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 app/controllers/mail.py diff --git a/app/controllers/mail.py b/app/controllers/mail.py new file mode 100644 index 0000000..51bbda5 --- /dev/null +++ b/app/controllers/mail.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from flask import request, jsonify, abort +from app import app +from app.services import processor + +logger = logging.getLogger(__name__) + + +@app.route("/inbox", methods=['POST']) +def new_mail(): + + try: + data = request.get_json() + logger.info(data) + + processor.enqueue({'request': 'new_mail', 'data': data}) + + except: + logger.exception("new mail failure") + abort(400) + + return "OK" diff --git a/app/run.py b/app/run.py index c513b6d..21652ea 100644 --- a/app/run.py +++ b/app/run.py @@ -20,6 +20,7 @@ import config from app.services import database from app.services import processor from app.controllers import api +from app.controllers import mail from app import app # configure logging diff --git a/app/services/processor.py b/app/services/processor.py index 5d0fbf9..a46bb78 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -36,7 +36,7 @@ class Processor(Thread): #elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: - logger.info("Dequeue unknown request " + msg) + logger.info("Dequeue unknown request " + str(msg)) def new_comment(data): diff --git a/config.py b/config.py index 8fef52b..0aab685 100644 --- a/config.py +++ b/config.py @@ -7,6 +7,8 @@ LANG = "en" #DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" +MAIL_URL = "http://localhost:8025" + HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 From 99b2eb3628c5bd6cc46f3b455861dc5b805799fc Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 16 May 2015 10:59:44 +0200 Subject: [PATCH 048/586] Connect Stacosys to SRMail --- app/controllers/mail.py | 25 +++++++++++++++++++++++++ app/run.py | 1 + app/services/processor.py | 2 +- config.py | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 app/controllers/mail.py diff --git a/app/controllers/mail.py b/app/controllers/mail.py new file mode 100644 index 0000000..51bbda5 --- /dev/null +++ b/app/controllers/mail.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from flask import request, jsonify, abort +from app import app +from app.services import processor + +logger = logging.getLogger(__name__) + + +@app.route("/inbox", methods=['POST']) +def new_mail(): + + try: + data = request.get_json() + logger.info(data) + + processor.enqueue({'request': 'new_mail', 'data': data}) + + except: + logger.exception("new mail failure") + abort(400) + + return "OK" diff --git a/app/run.py b/app/run.py index c513b6d..21652ea 100644 --- a/app/run.py +++ b/app/run.py @@ -20,6 +20,7 @@ import config from app.services import database from app.services import processor from app.controllers import api +from app.controllers import mail from app import app # configure logging diff --git a/app/services/processor.py b/app/services/processor.py index 5d0fbf9..a46bb78 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -36,7 +36,7 @@ class Processor(Thread): #elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: - logger.info("Dequeue unknown request " + msg) + logger.info("Dequeue unknown request " + str(msg)) def new_comment(data): diff --git a/config.py b/config.py index 8fef52b..0aab685 100644 --- a/config.py +++ b/config.py @@ -7,6 +7,8 @@ LANG = "en" #DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" +MAIL_URL = "http://localhost:8025" + HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 From 033e0821b205d87aa77899fba3efeaf8068df321 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 16 May 2015 18:12:18 +0200 Subject: [PATCH 049/586] Send comment by email via SRMail --- app/models/site.py | 1 + app/services/processor.py | 131 ++++++++++++++++++++------------------ config.py | 4 +- requirements.txt | 1 + tools/pecosys2stacosys.py | 9 +-- 5 files changed, 78 insertions(+), 68 deletions(-) diff --git a/app/models/site.py b/app/models/site.py index 413dd82..9c34cb2 100644 --- a/app/models/site.py +++ b/app/models/site.py @@ -10,6 +10,7 @@ class Site(Model): name = CharField(unique=True) url = CharField() token = CharField() + admin_email = CharField() class Meta: database = get_db() diff --git a/app/services/processor.py b/app/services/processor.py index a46bb78..a5f083c 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,6 +10,9 @@ import chardet from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment +import requests +import json +import config logger = logging.getLogger(__name__) @@ -28,59 +31,71 @@ class Processor(Thread): self.is_running = True while self.is_running: - msg = queue.get() - if msg['request'] == 'new_comment': - new_comment(msg['data']) - #elif msg['type'] == 'reply_comment_email': - # reply_comment_email(req['From'], req['Subject'], req['Body']) - #elif req['type'] == 'unsubscribe': - # unsubscribe_reader(req['email'], req['article']) - else: - logger.info("Dequeue unknown request " + str(msg)) + try: + msg = queue.get() + if msg['request'] == 'new_comment': + new_comment(msg['data']) + #elif msg['type'] == 'reply_comment_email': + # reply_comment_email(req['From'], req['Subject'], req['Body']) + #elif req['type'] == 'unsubscribe': + # unsubscribe_reader(req['email'], req['article']) + else: + logger.info("throw unknown request " + str(msg)) + except: + logger.exception("processing failure") def new_comment(data): - try: - token = data.get('token', '') - url = data.get('url', '') - author_name = data.get('author', '') - author_email = data.get('email', '') - author_site = data.get('site', '') - message = data.get('message', '') - subscribe = data.get('subscribe', '') + logger.info('new comment received: %s' % data) - # create a new comment row - site = Site.select().where(Site.token == token).get() - - logger.info('new comment received: %s' % data) + token = data.get('token', '') + url = data.get('url', '') + author_name = data.get('author', '') + author_email = data.get('email', '') + author_site = data.get('site', '') + message = data.get('message', '') + subscribe = data.get('subscribe', '') - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site + # create a new comment row + site = Site.select().where(Site.token == token).get() - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if author_site and author_site[:4] != 'http': + author_site = 'http://' + author_site - comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - content=message, created=created, published=None) - comment.save() + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - 1 / 0 - # Render email body template - email_body = get_template('new_comment').render(url=url, comment=comment) + # add a row to Comment table + comment = Comment(site=site, url=url, author_name=author_name, + author_site=author_site, author_email=author_email, + content=message, created=created, published=None) + comment.save() - # Send email - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - '[' + branch_name + '-' + article + ']', email_body) + # render email body template + comment_list = ( + 'author: %s' % author_name, + 'email: %s' % author_email, + 'site: %s' % author_site, + 'date: %s' % created, + 'url: %s' % url, + '', + '%s' % message, + '' + ) + comment_text = '\n'.join(comment_list) + email_body = get_template('new_comment').render(url=url, comment=comment_text) - # Reader subscribes to further comments - if subscribe and email: - subscribe_reader(email, article, url) + # send email + # TODO subject should embed a key + subject = '%s: %d' % (site.name, 1) + mail(site.admin_email, subject, email_body) - logger.debug("new comment processed ") - except: - logger.exception("new_comment failure") + # TODO support subscription + # Reader subscribes to further comments + #if subscribe and email: + # subscribe_reader(email, article, url) + + logger.debug("new comment processed ") def reply_comment_email(from_email, subject, message): @@ -184,31 +199,23 @@ def decode_best_effort(string): return string.decode(info['encoding'], errors='replace') -def mail(from_email, to_email, subject, *messages): +def mail(to_email, subject, message): - # Create the container (outer) email message. - msg = MIMEMultipart() - msg['Subject'] = subject - msg['From'] = from_email - msg['To'] = to_email - msg.preamble = subject - - for message in messages: - part = MIMEText(message, 'plain') - msg.attach(part) - - s = smtplib.SMTP(pecosys.get_config('smtp', 'host'), - pecosys.get_config('smtp', 'port')) - if(pecosys.get_config('smtp', 'starttls')): - s.starttls() - s.login(pecosys.get_config('smtp', 'login'), - pecosys.get_config('smtp', 'password')) - s.sendmail(from_email, to_email, msg.as_string()) - s.quit() + headers = {'Content-Type': 'application/json; charset=utf-8'} + msg = { + 'to': to_email, + 'subject': subject, + 'content': message + } + r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) + if r.status_code in (200, 201): + logger.debug('Email for %s posted' % to_email) + else: + logger.warn('Cannot post email for %s' % to_email) def get_template(name): - return env.get_template(pecosys.get_config('global', 'lang') + '/' + name + '.tpl') + return env.get_template(config.LANG + '/' + name + '.tpl') def enqueue(something): diff --git a/config.py b/config.py index 0aab685..174b316 100644 --- a/config.py +++ b/config.py @@ -2,12 +2,12 @@ DEBUG = True -LANG = "en" +LANG = "fr" #DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" -MAIL_URL = "http://localhost:8025" +MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 diff --git a/requirements.txt b/requirements.txt index cd3fc9b..c43096d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ Jinja2==2.7.3 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 +requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 408db43..bfb1b28 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -81,7 +81,7 @@ def convert_comment(db, site, root_url, filename): @provide_db -def convert(db, site_name, url, comment_dir): +def convert(db, site_name, url, admin_email, comment_dir): # create DB tables if needed db.create_tables([Site, Comment], safe=True) @@ -93,7 +93,8 @@ def convert(db, site_name, url, comment_dir): except Site.DoesNotExist: pass - site = Site.create(name=site_name, url=url, token=salt(url)) + site = Site.create(name=site_name, url=url, token=salt(url), + admin_email=admin_email) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: @@ -105,8 +106,8 @@ def convert(db, site_name, url, comment_dir): @clize -def pecosys2stacosys(site, url, comment_dir): - convert(site, url, comment_dir) +def pecosys2stacosys(site, url, admin_email, comment_dir): + convert(site, url, admin_email, comment_dir) if __name__ == '__main__': From 91344ab775f6efa46b27d6a3596320d2dd3d7cfe Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 16 May 2015 18:12:18 +0200 Subject: [PATCH 050/586] Send comment by email via SRMail --- app/models/site.py | 1 + app/services/processor.py | 131 ++++++++++++++++++++------------------ config.py | 4 +- requirements.txt | 1 + tools/pecosys2stacosys.py | 9 +-- 5 files changed, 78 insertions(+), 68 deletions(-) diff --git a/app/models/site.py b/app/models/site.py index 413dd82..9c34cb2 100644 --- a/app/models/site.py +++ b/app/models/site.py @@ -10,6 +10,7 @@ class Site(Model): name = CharField(unique=True) url = CharField() token = CharField() + admin_email = CharField() class Meta: database = get_db() diff --git a/app/services/processor.py b/app/services/processor.py index a46bb78..a5f083c 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,6 +10,9 @@ import chardet from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment +import requests +import json +import config logger = logging.getLogger(__name__) @@ -28,59 +31,71 @@ class Processor(Thread): self.is_running = True while self.is_running: - msg = queue.get() - if msg['request'] == 'new_comment': - new_comment(msg['data']) - #elif msg['type'] == 'reply_comment_email': - # reply_comment_email(req['From'], req['Subject'], req['Body']) - #elif req['type'] == 'unsubscribe': - # unsubscribe_reader(req['email'], req['article']) - else: - logger.info("Dequeue unknown request " + str(msg)) + try: + msg = queue.get() + if msg['request'] == 'new_comment': + new_comment(msg['data']) + #elif msg['type'] == 'reply_comment_email': + # reply_comment_email(req['From'], req['Subject'], req['Body']) + #elif req['type'] == 'unsubscribe': + # unsubscribe_reader(req['email'], req['article']) + else: + logger.info("throw unknown request " + str(msg)) + except: + logger.exception("processing failure") def new_comment(data): - try: - token = data.get('token', '') - url = data.get('url', '') - author_name = data.get('author', '') - author_email = data.get('email', '') - author_site = data.get('site', '') - message = data.get('message', '') - subscribe = data.get('subscribe', '') + logger.info('new comment received: %s' % data) - # create a new comment row - site = Site.select().where(Site.token == token).get() - - logger.info('new comment received: %s' % data) + token = data.get('token', '') + url = data.get('url', '') + author_name = data.get('author', '') + author_email = data.get('email', '') + author_site = data.get('site', '') + message = data.get('message', '') + subscribe = data.get('subscribe', '') - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site + # create a new comment row + site = Site.select().where(Site.token == token).get() - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if author_site and author_site[:4] != 'http': + author_site = 'http://' + author_site - comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - content=message, created=created, published=None) - comment.save() + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - 1 / 0 - # Render email body template - email_body = get_template('new_comment').render(url=url, comment=comment) + # add a row to Comment table + comment = Comment(site=site, url=url, author_name=author_name, + author_site=author_site, author_email=author_email, + content=message, created=created, published=None) + comment.save() - # Send email - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - '[' + branch_name + '-' + article + ']', email_body) + # render email body template + comment_list = ( + 'author: %s' % author_name, + 'email: %s' % author_email, + 'site: %s' % author_site, + 'date: %s' % created, + 'url: %s' % url, + '', + '%s' % message, + '' + ) + comment_text = '\n'.join(comment_list) + email_body = get_template('new_comment').render(url=url, comment=comment_text) - # Reader subscribes to further comments - if subscribe and email: - subscribe_reader(email, article, url) + # send email + # TODO subject should embed a key + subject = '%s: %d' % (site.name, 1) + mail(site.admin_email, subject, email_body) - logger.debug("new comment processed ") - except: - logger.exception("new_comment failure") + # TODO support subscription + # Reader subscribes to further comments + #if subscribe and email: + # subscribe_reader(email, article, url) + + logger.debug("new comment processed ") def reply_comment_email(from_email, subject, message): @@ -184,31 +199,23 @@ def decode_best_effort(string): return string.decode(info['encoding'], errors='replace') -def mail(from_email, to_email, subject, *messages): +def mail(to_email, subject, message): - # Create the container (outer) email message. - msg = MIMEMultipart() - msg['Subject'] = subject - msg['From'] = from_email - msg['To'] = to_email - msg.preamble = subject - - for message in messages: - part = MIMEText(message, 'plain') - msg.attach(part) - - s = smtplib.SMTP(pecosys.get_config('smtp', 'host'), - pecosys.get_config('smtp', 'port')) - if(pecosys.get_config('smtp', 'starttls')): - s.starttls() - s.login(pecosys.get_config('smtp', 'login'), - pecosys.get_config('smtp', 'password')) - s.sendmail(from_email, to_email, msg.as_string()) - s.quit() + headers = {'Content-Type': 'application/json; charset=utf-8'} + msg = { + 'to': to_email, + 'subject': subject, + 'content': message + } + r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) + if r.status_code in (200, 201): + logger.debug('Email for %s posted' % to_email) + else: + logger.warn('Cannot post email for %s' % to_email) def get_template(name): - return env.get_template(pecosys.get_config('global', 'lang') + '/' + name + '.tpl') + return env.get_template(config.LANG + '/' + name + '.tpl') def enqueue(something): diff --git a/config.py b/config.py index 0aab685..174b316 100644 --- a/config.py +++ b/config.py @@ -2,12 +2,12 @@ DEBUG = True -LANG = "en" +LANG = "fr" #DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" -MAIL_URL = "http://localhost:8025" +MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 diff --git a/requirements.txt b/requirements.txt index cd3fc9b..c43096d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ Jinja2==2.7.3 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 +requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index 408db43..bfb1b28 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -81,7 +81,7 @@ def convert_comment(db, site, root_url, filename): @provide_db -def convert(db, site_name, url, comment_dir): +def convert(db, site_name, url, admin_email, comment_dir): # create DB tables if needed db.create_tables([Site, Comment], safe=True) @@ -93,7 +93,8 @@ def convert(db, site_name, url, comment_dir): except Site.DoesNotExist: pass - site = Site.create(name=site_name, url=url, token=salt(url)) + site = Site.create(name=site_name, url=url, token=salt(url), + admin_email=admin_email) for dirpath, dirs, files in os.walk(comment_dir): for filename in files: @@ -105,8 +106,8 @@ def convert(db, site_name, url, comment_dir): @clize -def pecosys2stacosys(site, url, comment_dir): - convert(site, url, comment_dir) +def pecosys2stacosys(site, url, admin_email, comment_dir): + convert(site, url, admin_email, comment_dir) if __name__ == '__main__': From 8c1366ffaa2fc06a5290615a2b8a44839434e39f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 16 May 2015 18:23:40 +0200 Subject: [PATCH 051/586] Set comment id into email subject --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index a5f083c..53fa818 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: %d' % (site.name, 1) + subject = '%s: [%d]' % (site.name, comment.id) mail(site.admin_email, subject, email_body) # TODO support subscription From 893e096ce697f7b0bcf207655757e59e5fb9aba2 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 16 May 2015 18:23:40 +0200 Subject: [PATCH 052/586] Set comment id into email subject --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index a5f083c..53fa818 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: %d' % (site.name, 1) + subject = '%s: [%d]' % (site.name, comment.id) mail(site.admin_email, subject, email_body) # TODO support subscription From 2f4834054797a683240dee120d3843518080491c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 16 May 2015 20:55:55 +0200 Subject: [PATCH 053/586] Complete publishing process by email --- app/services/processor.py | 89 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 53fa818..f89774d 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -35,8 +35,8 @@ class Processor(Thread): msg = queue.get() if msg['request'] == 'new_comment': new_comment(msg['data']) - #elif msg['type'] == 'reply_comment_email': - # reply_comment_email(req['From'], req['Subject'], req['Body']) + elif msg['request'] == 'new_mail': + reply_comment_email(msg['data']) #elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: [%d]' % (site.name, comment.id) + subject = '%s: [%s:%d]' % (site.name, token, comment.id) mail(site.admin_email, subject, email_body) # TODO support subscription @@ -98,50 +98,57 @@ def new_comment(data): logger.debug("new comment processed ") -def reply_comment_email(from_email, subject, message): - try: - m = re.search('\[(\d+)\-(\w+)\]', subject) - branch_name = m.group(1) - article = m.group(2) +def reply_comment_email(data): - message = decode_best_effort(message) + email_address = data['from'] + subject = data['subject'] + message = '' + for part in data['parts']: + if part['content-type'] == 'text/plain': + message = part['content'] + break - # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() == 'NO': - logger.info('discard comment: %s' % branch_name) - email_body = get_template('drop_comment').render(original=message) - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - 'Re: ' + subject, email_body) - else: - if pecosys.get_config("git", "disabled"): - logger.debug("GIT usage disabled (debug mode)") - else: - git.merge(branch_name) - if pecosys.get_config("git", "remote"): - git.push() - logger.info('commit comment: %s' % branch_name) + m = re.search('\[(\w+)\:(\d+)\]', subject) + token = m.group(1) + comment_id = int(m.group(2)) - # send approval confirmation email to admin - email_body = get_template('approve_comment').render(original=message) - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - 'Re: ' + subject, email_body) + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == comment_id).get() + if comment.site.token != token: + logger.warn('ignore corrupted email') + return - # notify reader once comment is published - reader_email, article_url = get_email_metadata(message) - if reader_email: - notify_reader(reader_email, article_url) + # TODO validate chardet decoding is no more needed + #message = decode_best_effort(message) + if not message: + logger.warn('ignore empty email') + return - # notify subscribers every time a new comment is published - notify_subscribers(article) + # safe logic: no answer or unknown answer is a go for publishing + if message[:2].upper() == 'NO': + logger.info('discard comment: %d' % comment_id) + comment.delete_instance() + email_body = get_template('drop_comment').render(original=message) + mail(email_address, 'Re: ' + subject, email_body) + else: + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() - if pecosys.get_config("git", "disabled"): - logger.debug("GIT usage disabled (debug mode)") - else: - git.branch("-D", branch_name) - except: - logger.exception("new email failure") + logger.info('commit comment: %d' % comment_id) + + # send approval confirmation email to admin + email_body = get_template('approve_comment').render(original=message) + mail(email_address, 'Re: ' + subject, email_body) + + # TODO manage subscriptions + # notify reader once comment is published + #reader_email, article_url = get_email_metadata(message) + #if reader_email: + # notify_reader(reader_email, article_url) + + # notify subscribers every time a new comment is published + #notify_subscribers(article) def get_email_metadata(message): From 212d5a9442f2283690567793896b40c7e3bd6c9c Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 16 May 2015 20:55:55 +0200 Subject: [PATCH 054/586] Complete publishing process by email --- app/services/processor.py | 89 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 53fa818..f89774d 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -35,8 +35,8 @@ class Processor(Thread): msg = queue.get() if msg['request'] == 'new_comment': new_comment(msg['data']) - #elif msg['type'] == 'reply_comment_email': - # reply_comment_email(req['From'], req['Subject'], req['Body']) + elif msg['request'] == 'new_mail': + reply_comment_email(msg['data']) #elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: [%d]' % (site.name, comment.id) + subject = '%s: [%s:%d]' % (site.name, token, comment.id) mail(site.admin_email, subject, email_body) # TODO support subscription @@ -98,50 +98,57 @@ def new_comment(data): logger.debug("new comment processed ") -def reply_comment_email(from_email, subject, message): - try: - m = re.search('\[(\d+)\-(\w+)\]', subject) - branch_name = m.group(1) - article = m.group(2) +def reply_comment_email(data): - message = decode_best_effort(message) + email_address = data['from'] + subject = data['subject'] + message = '' + for part in data['parts']: + if part['content-type'] == 'text/plain': + message = part['content'] + break - # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() == 'NO': - logger.info('discard comment: %s' % branch_name) - email_body = get_template('drop_comment').render(original=message) - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - 'Re: ' + subject, email_body) - else: - if pecosys.get_config("git", "disabled"): - logger.debug("GIT usage disabled (debug mode)") - else: - git.merge(branch_name) - if pecosys.get_config("git", "remote"): - git.push() - logger.info('commit comment: %s' % branch_name) + m = re.search('\[(\w+)\:(\d+)\]', subject) + token = m.group(1) + comment_id = int(m.group(2)) - # send approval confirmation email to admin - email_body = get_template('approve_comment').render(original=message) - mail(pecosys.get_config('post', 'from_email'), - pecosys.get_config('post', 'to_email'), - 'Re: ' + subject, email_body) + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == comment_id).get() + if comment.site.token != token: + logger.warn('ignore corrupted email') + return - # notify reader once comment is published - reader_email, article_url = get_email_metadata(message) - if reader_email: - notify_reader(reader_email, article_url) + # TODO validate chardet decoding is no more needed + #message = decode_best_effort(message) + if not message: + logger.warn('ignore empty email') + return - # notify subscribers every time a new comment is published - notify_subscribers(article) + # safe logic: no answer or unknown answer is a go for publishing + if message[:2].upper() == 'NO': + logger.info('discard comment: %d' % comment_id) + comment.delete_instance() + email_body = get_template('drop_comment').render(original=message) + mail(email_address, 'Re: ' + subject, email_body) + else: + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() - if pecosys.get_config("git", "disabled"): - logger.debug("GIT usage disabled (debug mode)") - else: - git.branch("-D", branch_name) - except: - logger.exception("new email failure") + logger.info('commit comment: %d' % comment_id) + + # send approval confirmation email to admin + email_body = get_template('approve_comment').render(original=message) + mail(email_address, 'Re: ' + subject, email_body) + + # TODO manage subscriptions + # notify reader once comment is published + #reader_email, article_url = get_email_metadata(message) + #if reader_email: + # notify_reader(reader_email, article_url) + + # notify subscribers every time a new comment is published + #notify_subscribers(article) def get_email_metadata(message): From c23154fbabf570b6a2fe288327293bf459694189 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 May 2015 19:20:24 +0200 Subject: [PATCH 055/586] Finalize demo redirect page --- demo/public/js/page.js | 2 +- demo/public/redirect.html | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/demo/public/js/page.js b/demo/public/js/page.js index 06d6a97..727c65e 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -73,7 +73,7 @@ function new_comment() { function submit_success(data) { console.log('submit ' + data); - // TODO redirect to redirect page with page as argument + window.location="redirect.html?p=" + STACOSYS_PAGE; } function submit_failure(error) { diff --git a/demo/public/redirect.html b/demo/public/redirect.html index c7dc481..4d05f0c 100644 --- a/demo/public/redirect.html +++ b/demo/public/redirect.html @@ -8,18 +8,18 @@ Le blog du Yax - + - + - - + + - + @@ -44,23 +44,20 @@ From 019c78f2a197595722f2e31838089e01afcea591 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 May 2015 19:20:24 +0200 Subject: [PATCH 056/586] Finalize demo redirect page --- demo/public/js/page.js | 2 +- demo/public/redirect.html | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/demo/public/js/page.js b/demo/public/js/page.js index 06d6a97..727c65e 100644 --- a/demo/public/js/page.js +++ b/demo/public/js/page.js @@ -73,7 +73,7 @@ function new_comment() { function submit_success(data) { console.log('submit ' + data); - // TODO redirect to redirect page with page as argument + window.location="redirect.html?p=" + STACOSYS_PAGE; } function submit_failure(error) { diff --git a/demo/public/redirect.html b/demo/public/redirect.html index c7dc481..4d05f0c 100644 --- a/demo/public/redirect.html +++ b/demo/public/redirect.html @@ -8,18 +8,18 @@ Le blog du Yax - + - + - - + + - + @@ -44,23 +44,20 @@ From dfd1ea41d83e2ae4e38bc2e3686eec1ac8475ce0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 May 2015 19:26:19 +0200 Subject: [PATCH 057/586] Change admin email subject --- app/services/processor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index f89774d..0176627 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: [%s:%d]' % (site.name, token, comment.id) + subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) # TODO support subscription @@ -108,9 +108,9 @@ def reply_comment_email(data): message = part['content'] break - m = re.search('\[(\w+)\:(\d+)\]', subject) - token = m.group(1) - comment_id = int(m.group(2)) + m = re.search('\[(\d+)\:(\w+)\]', subject) + comment_id = int(m.group(1)) + token = m.group(2) # retrieve site and comment rows comment = Comment.select().where(Comment.id == comment_id).get() From ecda6c3217e15a0a3bbe51a25df1418b1867d164 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 May 2015 19:26:19 +0200 Subject: [PATCH 058/586] Change admin email subject --- app/services/processor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index f89774d..0176627 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -87,7 +87,7 @@ def new_comment(data): # send email # TODO subject should embed a key - subject = '%s: [%s:%d]' % (site.name, token, comment.id) + subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) # TODO support subscription @@ -108,9 +108,9 @@ def reply_comment_email(data): message = part['content'] break - m = re.search('\[(\w+)\:(\d+)\]', subject) - token = m.group(1) - comment_id = int(m.group(2)) + m = re.search('\[(\d+)\:(\w+)\]', subject) + comment_id = int(m.group(1)) + token = m.group(2) # retrieve site and comment rows comment = Comment.select().where(Comment.id == comment_id).get() From 58b499f9fe91daa90e86c747301fda8906948688 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 18 May 2015 13:22:49 +0200 Subject: [PATCH 059/586] Remove chardet dependency --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c43096d..c3ca49c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -chardet==2.3.0 clize==2.4 Flask==0.10.1 Flask-Cors==2.0.1 From c9307935dbd547ce9a601671d7ae56a2f2ff326c Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 18 May 2015 13:22:49 +0200 Subject: [PATCH 060/586] Remove chardet dependency --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c43096d..c3ca49c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -chardet==2.3.0 clize==2.4 Flask==0.10.1 Flask-Cors==2.0.1 From 224ece6be13e1d8dc739614702e8a7bded0f89ab Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 18 May 2015 13:23:24 +0200 Subject: [PATCH 061/586] PEP8 compliance --- app/controllers/api.py | 12 ++++++------ app/controllers/mail.py | 2 +- app/models/comment.py | 2 +- app/run.py | 10 ++++++++-- app/services/processor.py | 36 +++++++++++++----------------------- config.py | 2 +- 6 files changed, 30 insertions(+), 34 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0c9a765..f84f72c 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -22,9 +22,9 @@ def query_comments(): logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).order_by(+Comment.published): + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).order_by(+Comment.published): d = {} d['author'] = comment.author_name d['content'] = comment.content @@ -50,9 +50,9 @@ def get_comments_count(): token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() r = jsonify({'count': count}) r.status_code = 200 except: diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 51bbda5..81ec5d1 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort +from flask import request, abort from app import app from app.services import processor diff --git a/app/models/comment.py b/app/models/comment.py index 1b79a58..1492085 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,7 +13,7 @@ from app.services.database import get_db class Comment(Model): url = CharField() created = DateTimeField() - published = DateTimeField(null=True,default=None) + published = DateTimeField(null=True, default=None) author_name = CharField() author_email = CharField(default='') author_site = CharField(default='') diff --git a/app/run.py b/app/run.py index 21652ea..6823c3a 100644 --- a/app/run.py +++ b/app/run.py @@ -23,6 +23,7 @@ from app.controllers import api from app.controllers import mail from app import app + # configure logging def configure_logging(level): root_logger = logging.getLogger() @@ -30,7 +31,8 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + formatter = logging.Formatter( + '[%(asctime)s] %(name)s %(levelname)s %(message)s') # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -44,7 +46,11 @@ logger = logging.getLogger(__name__) # initialize database database.setup() -# start processor +# routes +logger.debug('imported: %s ' % api.__name__) +logger.debug('imported: %s ' % mail.__name__) + +# start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) diff --git a/app/services/processor.py b/app/services/processor.py index 0176627..aeeb235 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -6,7 +6,6 @@ import re from datetime import datetime from threading import Thread from queue import Queue -import chardet from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment @@ -37,7 +36,7 @@ class Processor(Thread): new_comment(msg['data']) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) - #elif req['type'] == 'unsubscribe': + # elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: logger.info("throw unknown request " + str(msg)) @@ -67,8 +66,8 @@ def new_comment(data): # add a row to Comment table comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - content=message, created=created, published=None) + author_site=author_site, author_email=author_email, + content=message, created=created, published=None) comment.save() # render email body template @@ -83,17 +82,18 @@ def new_comment(data): '' ) comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render(url=url, comment=comment_text) + email_body = get_template('new_comment').render( + url=url, comment=comment_text) # send email - # TODO subject should embed a key subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) - # TODO support subscription # Reader subscribes to further comments - #if subscribe and email: - # subscribe_reader(email, article, url) + if subscribe and author_email: + # TODO support subscription + # subscribe_reader(email, article, url) + pass logger.debug("new comment processed ") @@ -118,8 +118,6 @@ def reply_comment_email(data): logger.warn('ignore corrupted email') return - # TODO validate chardet decoding is no more needed - #message = decode_best_effort(message) if not message: logger.warn('ignore empty email') return @@ -131,7 +129,7 @@ def reply_comment_email(data): email_body = get_template('drop_comment').render(original=message) mail(email_address, 'Re: ' + subject, email_body) else: - # update Comment row + # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() @@ -143,12 +141,12 @@ def reply_comment_email(data): # TODO manage subscriptions # notify reader once comment is published - #reader_email, article_url = get_email_metadata(message) - #if reader_email: + # reader_email, article_url = get_email_metadata(message) + # if reader_email: # notify_reader(reader_email, article_url) # notify subscribers every time a new comment is published - #notify_subscribers(article) + # notify_subscribers(article) def get_email_metadata(message): @@ -198,14 +196,6 @@ def notify_reader(email, url): mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body) -def decode_best_effort(string): - info = chardet.detect(string) - if info['confidence'] < 0.5: - return string.decode('utf8', errors='replace') - else: - return string.decode(info['encoding'], errors='replace') - - def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} diff --git a/config.py b/config.py index 174b316..cf0c768 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,7 @@ DEBUG = True LANG = "fr" -#DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +# DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" From 6db8fac605d9ad8c41e82d324f5f7295e482043e Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 18 May 2015 13:23:24 +0200 Subject: [PATCH 062/586] PEP8 compliance --- app/controllers/api.py | 12 ++++++------ app/controllers/mail.py | 2 +- app/models/comment.py | 2 +- app/run.py | 10 ++++++++-- app/services/processor.py | 36 +++++++++++++----------------------- config.py | 2 +- 6 files changed, 30 insertions(+), 34 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0c9a765..f84f72c 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -22,9 +22,9 @@ def query_comments(): logger.info('retrieve comments for token %s, url %s' % (token, url)) for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).order_by(+Comment.published): + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).order_by(+Comment.published): d = {} d['author'] = comment.author_name d['content'] = comment.content @@ -50,9 +50,9 @@ def get_comments_count(): token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() r = jsonify({'count': count}) r.status_code = 200 except: diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 51bbda5..81ec5d1 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort +from flask import request, abort from app import app from app.services import processor diff --git a/app/models/comment.py b/app/models/comment.py index 1b79a58..1492085 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,7 +13,7 @@ from app.services.database import get_db class Comment(Model): url = CharField() created = DateTimeField() - published = DateTimeField(null=True,default=None) + published = DateTimeField(null=True, default=None) author_name = CharField() author_email = CharField(default='') author_site = CharField(default='') diff --git a/app/run.py b/app/run.py index 21652ea..6823c3a 100644 --- a/app/run.py +++ b/app/run.py @@ -23,6 +23,7 @@ from app.controllers import api from app.controllers import mail from app import app + # configure logging def configure_logging(level): root_logger = logging.getLogger() @@ -30,7 +31,8 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + formatter = logging.Formatter( + '[%(asctime)s] %(name)s %(levelname)s %(message)s') # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -44,7 +46,11 @@ logger = logging.getLogger(__name__) # initialize database database.setup() -# start processor +# routes +logger.debug('imported: %s ' % api.__name__) +logger.debug('imported: %s ' % mail.__name__) + +# start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) diff --git a/app/services/processor.py b/app/services/processor.py index 0176627..aeeb235 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -6,7 +6,6 @@ import re from datetime import datetime from threading import Thread from queue import Queue -import chardet from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment @@ -37,7 +36,7 @@ class Processor(Thread): new_comment(msg['data']) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) - #elif req['type'] == 'unsubscribe': + # elif req['type'] == 'unsubscribe': # unsubscribe_reader(req['email'], req['article']) else: logger.info("throw unknown request " + str(msg)) @@ -67,8 +66,8 @@ def new_comment(data): # add a row to Comment table comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - content=message, created=created, published=None) + author_site=author_site, author_email=author_email, + content=message, created=created, published=None) comment.save() # render email body template @@ -83,17 +82,18 @@ def new_comment(data): '' ) comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render(url=url, comment=comment_text) + email_body = get_template('new_comment').render( + url=url, comment=comment_text) # send email - # TODO subject should embed a key subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) - # TODO support subscription # Reader subscribes to further comments - #if subscribe and email: - # subscribe_reader(email, article, url) + if subscribe and author_email: + # TODO support subscription + # subscribe_reader(email, article, url) + pass logger.debug("new comment processed ") @@ -118,8 +118,6 @@ def reply_comment_email(data): logger.warn('ignore corrupted email') return - # TODO validate chardet decoding is no more needed - #message = decode_best_effort(message) if not message: logger.warn('ignore empty email') return @@ -131,7 +129,7 @@ def reply_comment_email(data): email_body = get_template('drop_comment').render(original=message) mail(email_address, 'Re: ' + subject, email_body) else: - # update Comment row + # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() @@ -143,12 +141,12 @@ def reply_comment_email(data): # TODO manage subscriptions # notify reader once comment is published - #reader_email, article_url = get_email_metadata(message) - #if reader_email: + # reader_email, article_url = get_email_metadata(message) + # if reader_email: # notify_reader(reader_email, article_url) # notify subscribers every time a new comment is published - #notify_subscribers(article) + # notify_subscribers(article) def get_email_metadata(message): @@ -198,14 +196,6 @@ def notify_reader(email, url): mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body) -def decode_best_effort(string): - info = chardet.detect(string) - if info['confidence'] < 0.5: - return string.decode('utf8', errors='replace') - else: - return string.decode(info['encoding'], errors='replace') - - def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} diff --git a/config.py b/config.py index 174b316..cf0c768 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,7 @@ DEBUG = True LANG = "fr" -#DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" +# DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" From e156ed6db784866f0351f0eb0d4994c2a2a9fdaa Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 24 May 2015 19:18:36 +0200 Subject: [PATCH 063/586] Update e-mail templates --- app/templates/en/approve_comment.tpl | 2 +- app/templates/en/drop_comment.tpl | 2 +- app/templates/en/new_comment.tpl | 2 +- app/templates/en/notify_reader.tpl | 2 +- app/templates/en/notify_subscriber.tpl | 2 +- app/templates/fr/approve_comment.tpl | 2 +- app/templates/fr/drop_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- app/templates/fr/notify_reader.tpl | 2 +- app/templates/fr/notify_subscriber.tpl | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/templates/en/approve_comment.tpl b/app/templates/en/approve_comment.tpl index 2daa5b2..145ca2c 100644 --- a/app/templates/en/approve_comment.tpl +++ b/app/templates/en/approve_comment.tpl @@ -3,7 +3,7 @@ Hi, The comment should be published soon. It has been approved. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/en/drop_comment.tpl b/app/templates/en/drop_comment.tpl index c3b0362..6aaed72 100644 --- a/app/templates/en/drop_comment.tpl +++ b/app/templates/en/drop_comment.tpl @@ -3,7 +3,7 @@ Hi, The comment will not be published. It has been dropped. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index a07f7a3..e043f92 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -13,4 +13,4 @@ Please find comment details below: {{ comment }} -- -Pecosys +Stacosys diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl index c2d2a0e..701b55f 100644 --- a/app/templates/en/notify_reader.tpl +++ b/app/templates/en/notify_reader.tpl @@ -5,5 +5,5 @@ Your comment has been approved. It should be published in few minutes. {{ article_url }} -- -Pecosys +Stacosys diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl index d1d9e6c..5a81758 100644 --- a/app/templates/en/notify_subscriber.tpl +++ b/app/templates/en/notify_subscriber.tpl @@ -9,5 +9,5 @@ You can unsubscribe at any time using this link: {{ unsubscribe_url }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/approve_comment.tpl b/app/templates/fr/approve_comment.tpl index a381c21..35668d4 100644 --- a/app/templates/fr/approve_comment.tpl +++ b/app/templates/fr/approve_comment.tpl @@ -3,7 +3,7 @@ Bonjour, Le commentaire sera bientôt publié. Il a été approuvé. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/fr/drop_comment.tpl b/app/templates/fr/drop_comment.tpl index 714decf..70e13ed 100644 --- a/app/templates/fr/drop_comment.tpl +++ b/app/templates/fr/drop_comment.tpl @@ -3,7 +3,7 @@ Bonjour, Le commentaire ne sera pas publié. Il a été rejeté. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index b3346fe..01ce664 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -13,4 +13,4 @@ Voici les détails concernant le commentaire : {{ comment }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl index c8d2956..9464cb0 100644 --- a/app/templates/fr/notify_reader.tpl +++ b/app/templates/fr/notify_reader.tpl @@ -5,5 +5,5 @@ Votre commentaire a été approuvé. Il sera publié dans quelques minutes. {{ article_url }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl index 29804ee..1d79f8a 100644 --- a/app/templates/fr/notify_subscriber.tpl +++ b/app/templates/fr/notify_subscriber.tpl @@ -9,5 +9,5 @@ Vous pouvez vous désinscrire à tout moment en suivant ce lien : {{ unsubscribe_url }} -- -Pecosys +Stacosys From ffab88e36978bc60e60b55050e119a82e5c3da70 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 24 May 2015 19:18:36 +0200 Subject: [PATCH 064/586] Update e-mail templates --- app/templates/en/approve_comment.tpl | 2 +- app/templates/en/drop_comment.tpl | 2 +- app/templates/en/new_comment.tpl | 2 +- app/templates/en/notify_reader.tpl | 2 +- app/templates/en/notify_subscriber.tpl | 2 +- app/templates/fr/approve_comment.tpl | 2 +- app/templates/fr/drop_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- app/templates/fr/notify_reader.tpl | 2 +- app/templates/fr/notify_subscriber.tpl | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/templates/en/approve_comment.tpl b/app/templates/en/approve_comment.tpl index 2daa5b2..145ca2c 100644 --- a/app/templates/en/approve_comment.tpl +++ b/app/templates/en/approve_comment.tpl @@ -3,7 +3,7 @@ Hi, The comment should be published soon. It has been approved. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/en/drop_comment.tpl b/app/templates/en/drop_comment.tpl index c3b0362..6aaed72 100644 --- a/app/templates/en/drop_comment.tpl +++ b/app/templates/en/drop_comment.tpl @@ -3,7 +3,7 @@ Hi, The comment will not be published. It has been dropped. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index a07f7a3..e043f92 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -13,4 +13,4 @@ Please find comment details below: {{ comment }} -- -Pecosys +Stacosys diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl index c2d2a0e..701b55f 100644 --- a/app/templates/en/notify_reader.tpl +++ b/app/templates/en/notify_reader.tpl @@ -5,5 +5,5 @@ Your comment has been approved. It should be published in few minutes. {{ article_url }} -- -Pecosys +Stacosys diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl index d1d9e6c..5a81758 100644 --- a/app/templates/en/notify_subscriber.tpl +++ b/app/templates/en/notify_subscriber.tpl @@ -9,5 +9,5 @@ You can unsubscribe at any time using this link: {{ unsubscribe_url }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/approve_comment.tpl b/app/templates/fr/approve_comment.tpl index a381c21..35668d4 100644 --- a/app/templates/fr/approve_comment.tpl +++ b/app/templates/fr/approve_comment.tpl @@ -3,7 +3,7 @@ Bonjour, Le commentaire sera bientôt publié. Il a été approuvé. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/fr/drop_comment.tpl b/app/templates/fr/drop_comment.tpl index 714decf..70e13ed 100644 --- a/app/templates/fr/drop_comment.tpl +++ b/app/templates/fr/drop_comment.tpl @@ -3,7 +3,7 @@ Bonjour, Le commentaire ne sera pas publié. Il a été rejeté. -- -Pecosys +Stacosys {{ original }} diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index b3346fe..01ce664 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -13,4 +13,4 @@ Voici les détails concernant le commentaire : {{ comment }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl index c8d2956..9464cb0 100644 --- a/app/templates/fr/notify_reader.tpl +++ b/app/templates/fr/notify_reader.tpl @@ -5,5 +5,5 @@ Votre commentaire a été approuvé. Il sera publié dans quelques minutes. {{ article_url }} -- -Pecosys +Stacosys diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl index 29804ee..1d79f8a 100644 --- a/app/templates/fr/notify_subscriber.tpl +++ b/app/templates/fr/notify_subscriber.tpl @@ -9,5 +9,5 @@ Vous pouvez vous désinscrire à tout moment en suivant ce lien : {{ unsubscribe_url }} -- -Pecosys +Stacosys From 9e1bf963c898f54439f51283da3a199d7c032e5a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 24 May 2015 19:40:46 +0200 Subject: [PATCH 065/586] Manage reader subscription --- app/controllers/mail.py | 2 +- app/controllers/reader.py | 29 +++++++++++++ app/models/reader.py | 17 ++++++++ app/run.py | 2 + app/services/database.py | 3 +- app/services/processor.py | 91 +++++++++++++++++++++------------------ config.py | 2 + 7 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 app/controllers/reader.py create mode 100644 app/models/reader.py diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 81ec5d1..7ade3fc 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -14,7 +14,7 @@ def new_mail(): try: data = request.get_json() - logger.info(data) + logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) diff --git a/app/controllers/reader.py b/app/controllers/reader.py new file mode 100644 index 0000000..2673105 --- /dev/null +++ b/app/controllers/reader.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from flask import request, abort +from app import app +from app.services import processor + +logger = logging.getLogger(__name__) + + +@app.route("/unsubscribe", methods=['GET']) +def unsubscribe(): + + try: + data = { + 'token': request.args.get('token', ''), + 'url': request.args.get('url', ''), + 'email': request.args.get('email', '') + } + logger.debug(data) + + processor.enqueue({'request': 'unsubscribe', 'data': data}) + + except: + logger.exception("unsubscribe failure") + abort(400) + + return "OK" diff --git a/app/models/reader.py b/app/models/reader.py new file mode 100644 index 0000000..a1eae15 --- /dev/null +++ b/app/models/reader.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import ForeignKeyField +from app.services.database import get_db +from app.models.site import Site + + +class Reader(Model): + url = CharField() + email = CharField(default='') + site = ForeignKeyField(Site, related_name='reader_site') + + class Meta: + database = get_db() diff --git a/app/run.py b/app/run.py index 6823c3a..a80df80 100644 --- a/app/run.py +++ b/app/run.py @@ -21,6 +21,7 @@ from app.services import database from app.services import processor from app.controllers import api from app.controllers import mail +from app.controllers import reader from app import app @@ -49,6 +50,7 @@ database.setup() # routes logger.debug('imported: %s ' % api.__name__) logger.debug('imported: %s ' % mail.__name__) +logger.debug('imported: %s ' % reader.__name__) # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) diff --git a/app/services/database.py b/app/services/database.py index f0fc403..5d17f27 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -23,5 +23,6 @@ def provide_db(func): def setup(db): from app.models.site import Site from app.models.comment import Comment + from app.models.reader import Reader - db.create_tables([Site, Comment], safe=True) + db.create_tables([Site, Comment, Reader], safe=True) diff --git a/app/services/processor.py b/app/services/processor.py index aeeb235..15df927 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -9,6 +9,7 @@ from queue import Queue from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment +from app.models.reader import Reader import requests import json import config @@ -36,8 +37,8 @@ class Processor(Thread): new_comment(msg['data']) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) - # elif req['type'] == 'unsubscribe': - # unsubscribe_reader(req['email'], req['article']) + elif msg['request'] == 'unsubscribe': + unsubscribe_reader(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -91,16 +92,14 @@ def new_comment(data): # Reader subscribes to further comments if subscribe and author_email: - # TODO support subscription - # subscribe_reader(email, article, url) - pass + subscribe_reader(author_email, token, url) logger.debug("new comment processed ") def reply_comment_email(data): - email_address = data['from'] + from_email = data['from'] subject = data['subject'] message = '' for part in data['parts']: @@ -127,7 +126,7 @@ def reply_comment_email(data): logger.info('discard comment: %d' % comment_id) comment.delete_instance() email_body = get_template('drop_comment').render(original=message) - mail(email_address, 'Re: ' + subject, email_body) + mail(from_email, 'Re: ' + subject, email_body) else: # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -137,63 +136,71 @@ def reply_comment_email(data): # send approval confirmation email to admin email_body = get_template('approve_comment').render(original=message) - mail(email_address, 'Re: ' + subject, email_body) + mail(from_email, 'Re: ' + subject, email_body) - # TODO manage subscriptions # notify reader once comment is published - # reader_email, article_url = get_email_metadata(message) - # if reader_email: - # notify_reader(reader_email, article_url) + reader_email = get_email_metadata(message) + if reader_email: + notify_reader(from_email, reader_email, comment.site.token, + comment.url) # notify subscribers every time a new comment is published - # notify_subscribers(article) + notify_subscribed_readers(comment.site.token, comment.url) def get_email_metadata(message): - # retrieve metadata reader email and URL from email body sent by admin + # retrieve metadata reader email from email body sent by admin email = "" - url = "" m = re.search('email:\s(.+@.+\..+)', message) if m: email = m.group(1) - - m = re.search('url:\s(.+)', message) - if m: - url = m.group(1) - return (email, url) + return email -def subscribe_reader(email, article, url): - logger.info("subscribe reader %s to %s (%s)" % (email, article, url)) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - db.insert({'email': email, 'article': article, 'url': url}) +def subscribe_reader(email, token, url): + logger.info('subscribe reader %s to %s [%s]' % (email, url, token)) + recorded = Reader.select().join(Site).where(Site.token == token, + Reader.email == email, + Reader.url == url).count() + if recorded: + logger.debug('reader %s is already recorded' % email) + else: + site = Site.select().where(Site.token == token).get() + reader = Reader(site=site, email=email, url=url) + reader.save() -def unsubscribe_reader(email, article): - logger.info("unsubscribe reader %s from %s" % (email, article)) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - db.remove((where('email') == email) & (where('article') == article)) +def unsubscribe_reader(data): + token = data.get('token', '') + url = data.get('url', '') + email = data.get('email', '') + logger.info('unsubscribe reader %s from %s (%s)' % (email, url, token)) + for reader in Reader.select().join(Site).where(Site.token == token, + Reader.email == email, + Reader.url == url): + reader.delete_instance() -def notify_subscribers(article): - logger.info('notify subscribers for article %s' % article) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - for item in db.search(where('article') == article): - logger.info(item) - to_email = item['email'] - logger.info("notify reader %s for article %s" % (to_email, article)) - unsubscribe_url = pecosys.get_config('subscription', 'url') + '?email=' + to_email + '&article=' + article - email_body = get_template('notify_subscriber').render(article_url=item['url'], - unsubscribe_url=unsubscribe_url) +def notify_subscribed_readers(token, url): + logger.info('notify subscribers for %s (%s)' % (url, token)) + for reader in Reader.select().join(Site).where(Site.token == token, + Reader.url == url): + to_email = reader.email + logger.info('notify reader %s' % to_email) + unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( + config.UNSUBSCRIBE_URL, to_email, token, reader.url) + email_body = get_template( + 'notify_subscriber').render(article_url=reader.url, + unsubscribe_url=unsubscribe_url) subject = get_template('notify_message').render() - mail(pecosys.get_config('subscription', 'from_email'), to_email, subject, email_body) + mail(to_email, subject, email_body) -def notify_reader(email, url): - logger.info('notify reader: email %s about URL %s' % (email, url)) +def notify_reader(from_email, to_email, token, url): + logger.info('notify reader: email %s about URL %s' % (to_email, url)) email_body = get_template('notify_reader').render(article_url=url) subject = get_template('notify_message').render() - mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body) + mail(to_email, subject, email_body) def mail(to_email, subject, message): diff --git a/config.py b/config.py index cf0c768..e5a2517 100644 --- a/config.py +++ b/config.py @@ -13,3 +13,5 @@ HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" + +UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' From e19aae121b6076926515ef25ce23d5bb01c37681 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 24 May 2015 19:40:46 +0200 Subject: [PATCH 066/586] Manage reader subscription --- app/controllers/mail.py | 2 +- app/controllers/reader.py | 29 +++++++++++++ app/models/reader.py | 17 ++++++++ app/run.py | 2 + app/services/database.py | 3 +- app/services/processor.py | 91 +++++++++++++++++++++------------------ config.py | 2 + 7 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 app/controllers/reader.py create mode 100644 app/models/reader.py diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 81ec5d1..7ade3fc 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -14,7 +14,7 @@ def new_mail(): try: data = request.get_json() - logger.info(data) + logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) diff --git a/app/controllers/reader.py b/app/controllers/reader.py new file mode 100644 index 0000000..2673105 --- /dev/null +++ b/app/controllers/reader.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +from flask import request, abort +from app import app +from app.services import processor + +logger = logging.getLogger(__name__) + + +@app.route("/unsubscribe", methods=['GET']) +def unsubscribe(): + + try: + data = { + 'token': request.args.get('token', ''), + 'url': request.args.get('url', ''), + 'email': request.args.get('email', '') + } + logger.debug(data) + + processor.enqueue({'request': 'unsubscribe', 'data': data}) + + except: + logger.exception("unsubscribe failure") + abort(400) + + return "OK" diff --git a/app/models/reader.py b/app/models/reader.py new file mode 100644 index 0000000..a1eae15 --- /dev/null +++ b/app/models/reader.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import ForeignKeyField +from app.services.database import get_db +from app.models.site import Site + + +class Reader(Model): + url = CharField() + email = CharField(default='') + site = ForeignKeyField(Site, related_name='reader_site') + + class Meta: + database = get_db() diff --git a/app/run.py b/app/run.py index 6823c3a..a80df80 100644 --- a/app/run.py +++ b/app/run.py @@ -21,6 +21,7 @@ from app.services import database from app.services import processor from app.controllers import api from app.controllers import mail +from app.controllers import reader from app import app @@ -49,6 +50,7 @@ database.setup() # routes logger.debug('imported: %s ' % api.__name__) logger.debug('imported: %s ' % mail.__name__) +logger.debug('imported: %s ' % reader.__name__) # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) diff --git a/app/services/database.py b/app/services/database.py index f0fc403..5d17f27 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -23,5 +23,6 @@ def provide_db(func): def setup(db): from app.models.site import Site from app.models.comment import Comment + from app.models.reader import Reader - db.create_tables([Site, Comment], safe=True) + db.create_tables([Site, Comment, Reader], safe=True) diff --git a/app/services/processor.py b/app/services/processor.py index aeeb235..15df927 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -9,6 +9,7 @@ from queue import Queue from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment +from app.models.reader import Reader import requests import json import config @@ -36,8 +37,8 @@ class Processor(Thread): new_comment(msg['data']) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) - # elif req['type'] == 'unsubscribe': - # unsubscribe_reader(req['email'], req['article']) + elif msg['request'] == 'unsubscribe': + unsubscribe_reader(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -91,16 +92,14 @@ def new_comment(data): # Reader subscribes to further comments if subscribe and author_email: - # TODO support subscription - # subscribe_reader(email, article, url) - pass + subscribe_reader(author_email, token, url) logger.debug("new comment processed ") def reply_comment_email(data): - email_address = data['from'] + from_email = data['from'] subject = data['subject'] message = '' for part in data['parts']: @@ -127,7 +126,7 @@ def reply_comment_email(data): logger.info('discard comment: %d' % comment_id) comment.delete_instance() email_body = get_template('drop_comment').render(original=message) - mail(email_address, 'Re: ' + subject, email_body) + mail(from_email, 'Re: ' + subject, email_body) else: # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -137,63 +136,71 @@ def reply_comment_email(data): # send approval confirmation email to admin email_body = get_template('approve_comment').render(original=message) - mail(email_address, 'Re: ' + subject, email_body) + mail(from_email, 'Re: ' + subject, email_body) - # TODO manage subscriptions # notify reader once comment is published - # reader_email, article_url = get_email_metadata(message) - # if reader_email: - # notify_reader(reader_email, article_url) + reader_email = get_email_metadata(message) + if reader_email: + notify_reader(from_email, reader_email, comment.site.token, + comment.url) # notify subscribers every time a new comment is published - # notify_subscribers(article) + notify_subscribed_readers(comment.site.token, comment.url) def get_email_metadata(message): - # retrieve metadata reader email and URL from email body sent by admin + # retrieve metadata reader email from email body sent by admin email = "" - url = "" m = re.search('email:\s(.+@.+\..+)', message) if m: email = m.group(1) - - m = re.search('url:\s(.+)', message) - if m: - url = m.group(1) - return (email, url) + return email -def subscribe_reader(email, article, url): - logger.info("subscribe reader %s to %s (%s)" % (email, article, url)) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - db.insert({'email': email, 'article': article, 'url': url}) +def subscribe_reader(email, token, url): + logger.info('subscribe reader %s to %s [%s]' % (email, url, token)) + recorded = Reader.select().join(Site).where(Site.token == token, + Reader.email == email, + Reader.url == url).count() + if recorded: + logger.debug('reader %s is already recorded' % email) + else: + site = Site.select().where(Site.token == token).get() + reader = Reader(site=site, email=email, url=url) + reader.save() -def unsubscribe_reader(email, article): - logger.info("unsubscribe reader %s from %s" % (email, article)) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - db.remove((where('email') == email) & (where('article') == article)) +def unsubscribe_reader(data): + token = data.get('token', '') + url = data.get('url', '') + email = data.get('email', '') + logger.info('unsubscribe reader %s from %s (%s)' % (email, url, token)) + for reader in Reader.select().join(Site).where(Site.token == token, + Reader.email == email, + Reader.url == url): + reader.delete_instance() -def notify_subscribers(article): - logger.info('notify subscribers for article %s' % article) - db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json') - for item in db.search(where('article') == article): - logger.info(item) - to_email = item['email'] - logger.info("notify reader %s for article %s" % (to_email, article)) - unsubscribe_url = pecosys.get_config('subscription', 'url') + '?email=' + to_email + '&article=' + article - email_body = get_template('notify_subscriber').render(article_url=item['url'], - unsubscribe_url=unsubscribe_url) +def notify_subscribed_readers(token, url): + logger.info('notify subscribers for %s (%s)' % (url, token)) + for reader in Reader.select().join(Site).where(Site.token == token, + Reader.url == url): + to_email = reader.email + logger.info('notify reader %s' % to_email) + unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( + config.UNSUBSCRIBE_URL, to_email, token, reader.url) + email_body = get_template( + 'notify_subscriber').render(article_url=reader.url, + unsubscribe_url=unsubscribe_url) subject = get_template('notify_message').render() - mail(pecosys.get_config('subscription', 'from_email'), to_email, subject, email_body) + mail(to_email, subject, email_body) -def notify_reader(email, url): - logger.info('notify reader: email %s about URL %s' % (email, url)) +def notify_reader(from_email, to_email, token, url): + logger.info('notify reader: email %s about URL %s' % (to_email, url)) email_body = get_template('notify_reader').render(article_url=url) subject = get_template('notify_message').render() - mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body) + mail(to_email, subject, email_body) def mail(to_email, subject, message): diff --git a/config.py b/config.py index cf0c768..e5a2517 100644 --- a/config.py +++ b/config.py @@ -13,3 +13,5 @@ HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" + +UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' From 1f590f5c84a9e92c306afca48dc4cd9c75cd6763 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 May 2015 22:27:13 +0200 Subject: [PATCH 067/586] Use windows.location.pathname as page id --- tools/pecosys2stacosys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index bfb1b28..0d5d26b 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -69,8 +69,8 @@ def convert_comment(db, site, root_url, filename): if 'url' in d: url = remove_from_string(d['url'], 'https://') url = remove_from_string(url, 'http://') - url = remove_from_string(url, root_url) - comment.url = remove_from_string(url, '/') + comment.url = remove_from_string(url, root_url) + # comment.url = remove_from_string(url, '/') # else: # comment.url = d['article'] if 'date' in d: From 38e632e9385f8f858e14cd6c58ad90be57e4dc3a Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 26 May 2015 22:27:13 +0200 Subject: [PATCH 068/586] Use windows.location.pathname as page id --- tools/pecosys2stacosys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py index bfb1b28..0d5d26b 100644 --- a/tools/pecosys2stacosys.py +++ b/tools/pecosys2stacosys.py @@ -69,8 +69,8 @@ def convert_comment(db, site, root_url, filename): if 'url' in d: url = remove_from_string(d['url'], 'https://') url = remove_from_string(url, 'http://') - url = remove_from_string(url, root_url) - comment.url = remove_from_string(url, '/') + comment.url = remove_from_string(url, root_url) + # comment.url = remove_from_string(url, '/') # else: # comment.url = d['article'] if 'date' in d: From 7e2ab4652d47998b0a6dc5c578b2ac64eb593504 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 5 Aug 2015 11:16:38 +0200 Subject: [PATCH 069/586] Fix typo --- app/templates/en/new_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index e043f92..3e176e4 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -6,7 +6,7 @@ You have two choices: - reject the comment by replying NO (or no), - accept the comment by sending back the email as it is. -If you choose the latter option, Pecosys is going to publish the commennt. +If you choose the latter option, Stacosys is going to publish the commennt. Please find comment details below: diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index 01ce664..ae84899 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -6,7 +6,7 @@ Vous avez deux réponses possibles : - rejeter le commentaire en répondant NO (ou no), - accepter le commentaire en renvoyant cet email tel quel. -Si cette dernière option est choisie, Pecosys publiera le commentaire très bientôt. +Si cette dernière option est choisie, Stacosys publiera le commentaire très bientôt. Voici les détails concernant le commentaire : From b2f9cb596f2bb6b1dd28a8f33fd2e5430f014e12 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 5 Aug 2015 11:16:38 +0200 Subject: [PATCH 070/586] Fix typo --- app/templates/en/new_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index e043f92..3e176e4 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -6,7 +6,7 @@ You have two choices: - reject the comment by replying NO (or no), - accept the comment by sending back the email as it is. -If you choose the latter option, Pecosys is going to publish the commennt. +If you choose the latter option, Stacosys is going to publish the commennt. Please find comment details below: diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index 01ce664..ae84899 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -6,7 +6,7 @@ Vous avez deux réponses possibles : - rejeter le commentaire en répondant NO (ou no), - accepter le commentaire en renvoyant cet email tel quel. -Si cette dernière option est choisie, Pecosys publiera le commentaire très bientôt. +Si cette dernière option est choisie, Stacosys publiera le commentaire très bientôt. Voici les détails concernant le commentaire : From c1acd3a34ac2b6a89896ab1801f543a45afb5160 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 4 Sep 2015 12:58:59 +0200 Subject: [PATCH 071/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 15df927..f2d3495 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe and author_email: + if subscribe = 'true' and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From cbf47e7f78148ef5621cb5ab7500a654e28c52db Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 4 Sep 2015 12:58:59 +0200 Subject: [PATCH 072/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 15df927..f2d3495 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe and author_email: + if subscribe = 'true' and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From 79295cfc9ddb1f84222977d3e3fb9b67840b8ccc Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 4 Sep 2015 13:03:41 +0200 Subject: [PATCH 073/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index f2d3495..c464b54 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe = 'true' and author_email: + if subscribe == 'true' and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From 17825370069214f3fcaf1bab26e4b40e975dc66c Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 4 Sep 2015 13:03:41 +0200 Subject: [PATCH 074/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index f2d3495..c464b54 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe = 'true' and author_email: + if subscribe == 'true' and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From 1a9e19fa33ba75e310dafc85c67db5cd3d2d16f8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 4 Sep 2015 13:26:55 +0200 Subject: [PATCH 075/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index c464b54..15df927 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe == 'true' and author_email: + if subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From d4dd8ac3077890e87e1ee64fec8ff5c6589013e1 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 4 Sep 2015 13:26:55 +0200 Subject: [PATCH 076/586] Fix comment subscription --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index c464b54..15df927 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -91,7 +91,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe == 'true' and author_email: + if subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") From 7f255eb48a4455b080b2b215bd58a0428820c234 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 5 Sep 2015 15:57:45 +0200 Subject: [PATCH 077/586] Strip comment attributes --- app/services/processor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 15df927..dfe3cfb 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -51,9 +51,9 @@ def new_comment(data): token = data.get('token', '') url = data.get('url', '') - author_name = data.get('author', '') - author_email = data.get('email', '') - author_site = data.get('site', '') + author_name = data.get('author', '').strip() + author_email = data.get('email', '').strip() + author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') From 890e18e9d590eff66f551b5cda5cc3c4daf5eba9 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Sep 2015 15:57:45 +0200 Subject: [PATCH 078/586] Strip comment attributes --- app/services/processor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 15df927..dfe3cfb 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -51,9 +51,9 @@ def new_comment(data): token = data.get('token', '') url = data.get('url', '') - author_name = data.get('author', '') - author_email = data.get('email', '') - author_site = data.get('site', '') + author_name = data.get('author', '').strip() + author_email = data.get('email', '').strip() + author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') From efc620aee84f5849f5e190d3eb8ccaabe5ed2435 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 6 Sep 2015 18:58:07 +0200 Subject: [PATCH 079/586] Draft reporting --- app/controllers/api.py | 26 ++++++++++++++++++++++++++ app/models/report.py | 22 ++++++++++++++++++++++ app/services/database.py | 3 ++- app/services/processor.py | 37 +++++++++++++++++++++++++++++++++++++ config.py | 2 ++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 app/models/report.py diff --git a/app/controllers/api.py b/app/controllers/api.py index f84f72c..1389155 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import logging +import config from flask import request, jsonify, abort from app import app from app.models.site import Site @@ -88,3 +89,28 @@ def new_comment(): abort(400) return "OK" + + +@app.route("/report", methods=['GET']) +def report(): + + try: + token = request.args.get('token', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(404) + + processor.enqueue({'request': 'report', 'data': token}) + + except: + logger.exception("report failure") + abort(500) + + return "OK" \ No newline at end of file diff --git a/app/models/report.py b/app/models/report.py new file mode 100644 index 0000000..e6102d1 --- /dev/null +++ b/app/models/report.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import BooleanField +from peewee import ForeignKeyField +from app.services.database import get_db +from app.models.site import Site + +class Report(Model): + name = CharField(unique=True) + email = CharField() + url = CharField() + published = BooleanField() + rejected = BooleanField() + subscribed = BooleanField() + unsubscribed = BooleanField() + site = ForeignKeyField(Site, related_name='report_site') + + class Meta: + database = get_db() diff --git a/app/services/database.py b/app/services/database.py index 5d17f27..0075e13 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -24,5 +24,6 @@ def setup(db): from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader + from app.models.report import Report - db.create_tables([Site, Comment, Reader], safe=True) + db.create_tables([Site, Comment, Reader, Report], safe=True) diff --git a/app/services/processor.py b/app/services/processor.py index dfe3cfb..a8199d1 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -39,6 +39,8 @@ class Processor(Thread): reply_comment_email(msg['data']) elif msg['request'] == 'unsubscribe': unsubscribe_reader(msg['data']) + elif msg['request'] == 'report': + report(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -123,11 +125,17 @@ def reply_comment_email(data): # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': + # report event + report_rejected(comment) + logger.info('discard comment: %d' % comment_id) comment.delete_instance() email_body = get_template('drop_comment').render(original=message) mail(from_email, 'Re: ' + subject, email_body) else: + # report event + report_published(comment) + # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() @@ -169,6 +177,9 @@ def subscribe_reader(email, token, url): reader = Reader(site=site, email=email, url=url) reader.save() + # report event + report_subscribed(reader) + def unsubscribe_reader(data): token = data.get('token', '') @@ -178,6 +189,9 @@ def unsubscribe_reader(data): for reader in Reader.select().join(Site).where(Site.token == token, Reader.email == email, Reader.url == url): + # report event + report_unsubscribed(reader) + reader.delete_instance() @@ -203,6 +217,29 @@ def notify_reader(from_email, to_email, token, url): mail(to_email, subject, email_body) +def report_rejected(comment): + pass + + +def report_published(comment): + pass + + +def report_subscribed(comment): + pass + + +def report_unsubscribed(comment): + pass + + +def report(token): + print('report requested for {}'.format(token)) + standby_count = Comment.select().join(Site).where( + Site.token == token, Comment.published.is_null(True)).count() + print('standby {}'.format(standby_count)) + + def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} diff --git a/config.py b/config.py index e5a2517..7e5298c 100644 --- a/config.py +++ b/config.py @@ -14,4 +14,6 @@ HTTP_PORT = 8000 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" +SECRET = "Uqca5Kc8xuU6THz9" + UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' From 49c40838823723c0094b7f2c0b116c0c1d00823c Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 6 Sep 2015 18:58:07 +0200 Subject: [PATCH 080/586] Draft reporting --- app/controllers/api.py | 26 ++++++++++++++++++++++++++ app/models/report.py | 22 ++++++++++++++++++++++ app/services/database.py | 3 ++- app/services/processor.py | 37 +++++++++++++++++++++++++++++++++++++ config.py | 2 ++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 app/models/report.py diff --git a/app/controllers/api.py b/app/controllers/api.py index f84f72c..1389155 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import logging +import config from flask import request, jsonify, abort from app import app from app.models.site import Site @@ -88,3 +89,28 @@ def new_comment(): abort(400) return "OK" + + +@app.route("/report", methods=['GET']) +def report(): + + try: + token = request.args.get('token', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(404) + + processor.enqueue({'request': 'report', 'data': token}) + + except: + logger.exception("report failure") + abort(500) + + return "OK" \ No newline at end of file diff --git a/app/models/report.py b/app/models/report.py new file mode 100644 index 0000000..e6102d1 --- /dev/null +++ b/app/models/report.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from peewee import Model +from peewee import CharField +from peewee import BooleanField +from peewee import ForeignKeyField +from app.services.database import get_db +from app.models.site import Site + +class Report(Model): + name = CharField(unique=True) + email = CharField() + url = CharField() + published = BooleanField() + rejected = BooleanField() + subscribed = BooleanField() + unsubscribed = BooleanField() + site = ForeignKeyField(Site, related_name='report_site') + + class Meta: + database = get_db() diff --git a/app/services/database.py b/app/services/database.py index 5d17f27..0075e13 100644 --- a/app/services/database.py +++ b/app/services/database.py @@ -24,5 +24,6 @@ def setup(db): from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader + from app.models.report import Report - db.create_tables([Site, Comment, Reader], safe=True) + db.create_tables([Site, Comment, Reader, Report], safe=True) diff --git a/app/services/processor.py b/app/services/processor.py index dfe3cfb..a8199d1 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -39,6 +39,8 @@ class Processor(Thread): reply_comment_email(msg['data']) elif msg['request'] == 'unsubscribe': unsubscribe_reader(msg['data']) + elif msg['request'] == 'report': + report(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -123,11 +125,17 @@ def reply_comment_email(data): # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': + # report event + report_rejected(comment) + logger.info('discard comment: %d' % comment_id) comment.delete_instance() email_body = get_template('drop_comment').render(original=message) mail(from_email, 'Re: ' + subject, email_body) else: + # report event + report_published(comment) + # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() @@ -169,6 +177,9 @@ def subscribe_reader(email, token, url): reader = Reader(site=site, email=email, url=url) reader.save() + # report event + report_subscribed(reader) + def unsubscribe_reader(data): token = data.get('token', '') @@ -178,6 +189,9 @@ def unsubscribe_reader(data): for reader in Reader.select().join(Site).where(Site.token == token, Reader.email == email, Reader.url == url): + # report event + report_unsubscribed(reader) + reader.delete_instance() @@ -203,6 +217,29 @@ def notify_reader(from_email, to_email, token, url): mail(to_email, subject, email_body) +def report_rejected(comment): + pass + + +def report_published(comment): + pass + + +def report_subscribed(comment): + pass + + +def report_unsubscribed(comment): + pass + + +def report(token): + print('report requested for {}'.format(token)) + standby_count = Comment.select().join(Site).where( + Site.token == token, Comment.published.is_null(True)).count() + print('standby {}'.format(standby_count)) + + def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} diff --git a/config.py b/config.py index e5a2517..7e5298c 100644 --- a/config.py +++ b/config.py @@ -14,4 +14,6 @@ HTTP_PORT = 8000 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" +SECRET = "Uqca5Kc8xuU6THz9" + UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' From 00bb418fe4f707d98000de97ba91a3f5ec367fe0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 20:23:53 +0200 Subject: [PATCH 081/586] Add report feature --- app/models/report.py | 8 +++--- app/services/processor.py | 41 ++++++++++++++++++++++++----- app/templates/en/report.tpl | 11 ++++++++ app/templates/en/report_message.tpl | 1 + app/templates/fr/report.tpl | 11 ++++++++ app/templates/fr/report_message.tpl | 1 + 6 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 app/templates/en/report.tpl create mode 100644 app/templates/en/report_message.tpl create mode 100644 app/templates/fr/report.tpl create mode 100644 app/templates/fr/report_message.tpl diff --git a/app/models/report.py b/app/models/report.py index e6102d1..a72409d 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -12,10 +12,10 @@ class Report(Model): name = CharField(unique=True) email = CharField() url = CharField() - published = BooleanField() - rejected = BooleanField() - subscribed = BooleanField() - unsubscribed = BooleanField() + published = BooleanField(default=False) + rejected = BooleanField(default=False) + subscribed = BooleanField(default=False) + unsubscribed = BooleanField(default=False) site = ForeignKeyField(Site, related_name='report_site') class Meta: diff --git a/app/services/processor.py b/app/services/processor.py index a8199d1..1fb29a4 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,6 +10,7 @@ from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader +from app.models.report import Report import requests import json import config @@ -218,26 +219,52 @@ def notify_reader(from_email, to_email, token, url): def report_rejected(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + rejected=True) + report.save() def report_published(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + published=True) + report.save() def report_subscribed(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + subscribed=True) + report.save() def report_unsubscribed(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + unsubscribed=True) + report.save() def report(token): - print('report requested for {}'.format(token)) - standby_count = Comment.select().join(Site).where( + site = Site.select().where(Site.token == token).get() + c_standby = Comment.select().join(Site).where( Site.token == token, Comment.published.is_null(True)).count() - print('standby {}'.format(standby_count)) + c_published = Report.select().join(Site).where( + Site.token == token, Report.published).count() + c_rejected = Report.select().join(Site).where( + Site.token == token, Report.rejected).count() + c_subscribed = Report.select().join(Site).where( + Site.token == token, Report.subscribed).count() + c_unsubscribed = Report.select().join(Site).where( + Site.token == token, Report.unsubscribed).count() + email_body = get_template('report').render(standby=c_standby, published=c_published, rejected=c_rejected, + subscribed=c_subscribed,unsubscribed=c_unsubscribed) + subject = get_template('report_message').render(site=site.name) + mail(site.admin_email, subject, email_body) + + # TODO: delete report table + #Report.delete().execute() def mail(to_email, subject, message): diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl new file mode 100644 index 0000000..188edb8 --- /dev/null +++ b/app/templates/en/report.tpl @@ -0,0 +1,11 @@ +Comments : +- published : {{ published }} +- rejected : {{ rejected }} +- standby : {{ standby }} + +New readers : {{ subscribed }} +Cancelled subscriptions : {{ unsubscribed }} + +-- +Stacosys + diff --git a/app/templates/en/report_message.tpl b/app/templates/en/report_message.tpl new file mode 100644 index 0000000..c1b5056 --- /dev/null +++ b/app/templates/en/report_message.tpl @@ -0,0 +1 @@ +Status report : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl new file mode 100644 index 0000000..0445295 --- /dev/null +++ b/app/templates/fr/report.tpl @@ -0,0 +1,11 @@ +Nombre de commentaires : +- publié(s) : {{ published }} +- rejeté(s) : {{ rejected }} +- en attente : {{ standby }} + +Nombre de nouveaux abonnés : {{ subscribed }} +Abonnements résiliés : {{ unsubscribed }} + +-- +Stacosys + diff --git a/app/templates/fr/report_message.tpl b/app/templates/fr/report_message.tpl new file mode 100644 index 0000000..45f08e7 --- /dev/null +++ b/app/templates/fr/report_message.tpl @@ -0,0 +1 @@ +Rapport d'activité : {{ site }} \ No newline at end of file From 18ad5870b7ece59a2d9ba6fe97c8c5640f3ca345 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 20:23:53 +0200 Subject: [PATCH 082/586] Add report feature --- app/models/report.py | 8 +++--- app/services/processor.py | 41 ++++++++++++++++++++++++----- app/templates/en/report.tpl | 11 ++++++++ app/templates/en/report_message.tpl | 1 + app/templates/fr/report.tpl | 11 ++++++++ app/templates/fr/report_message.tpl | 1 + 6 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 app/templates/en/report.tpl create mode 100644 app/templates/en/report_message.tpl create mode 100644 app/templates/fr/report.tpl create mode 100644 app/templates/fr/report_message.tpl diff --git a/app/models/report.py b/app/models/report.py index e6102d1..a72409d 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -12,10 +12,10 @@ class Report(Model): name = CharField(unique=True) email = CharField() url = CharField() - published = BooleanField() - rejected = BooleanField() - subscribed = BooleanField() - unsubscribed = BooleanField() + published = BooleanField(default=False) + rejected = BooleanField(default=False) + subscribed = BooleanField(default=False) + unsubscribed = BooleanField(default=False) site = ForeignKeyField(Site, related_name='report_site') class Meta: diff --git a/app/services/processor.py b/app/services/processor.py index a8199d1..1fb29a4 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,6 +10,7 @@ from jinja2 import Environment, FileSystemLoader from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader +from app.models.report import Report import requests import json import config @@ -218,26 +219,52 @@ def notify_reader(from_email, to_email, token, url): def report_rejected(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + rejected=True) + report.save() def report_published(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + published=True) + report.save() def report_subscribed(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + subscribed=True) + report.save() def report_unsubscribed(comment): - pass + report = Report(site=comment.site, url=comment.url, + name=comment.author_name, email=comment.author_email, + unsubscribed=True) + report.save() def report(token): - print('report requested for {}'.format(token)) - standby_count = Comment.select().join(Site).where( + site = Site.select().where(Site.token == token).get() + c_standby = Comment.select().join(Site).where( Site.token == token, Comment.published.is_null(True)).count() - print('standby {}'.format(standby_count)) + c_published = Report.select().join(Site).where( + Site.token == token, Report.published).count() + c_rejected = Report.select().join(Site).where( + Site.token == token, Report.rejected).count() + c_subscribed = Report.select().join(Site).where( + Site.token == token, Report.subscribed).count() + c_unsubscribed = Report.select().join(Site).where( + Site.token == token, Report.unsubscribed).count() + email_body = get_template('report').render(standby=c_standby, published=c_published, rejected=c_rejected, + subscribed=c_subscribed,unsubscribed=c_unsubscribed) + subject = get_template('report_message').render(site=site.name) + mail(site.admin_email, subject, email_body) + + # TODO: delete report table + #Report.delete().execute() def mail(to_email, subject, message): diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl new file mode 100644 index 0000000..188edb8 --- /dev/null +++ b/app/templates/en/report.tpl @@ -0,0 +1,11 @@ +Comments : +- published : {{ published }} +- rejected : {{ rejected }} +- standby : {{ standby }} + +New readers : {{ subscribed }} +Cancelled subscriptions : {{ unsubscribed }} + +-- +Stacosys + diff --git a/app/templates/en/report_message.tpl b/app/templates/en/report_message.tpl new file mode 100644 index 0000000..c1b5056 --- /dev/null +++ b/app/templates/en/report_message.tpl @@ -0,0 +1 @@ +Status report : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl new file mode 100644 index 0000000..0445295 --- /dev/null +++ b/app/templates/fr/report.tpl @@ -0,0 +1,11 @@ +Nombre de commentaires : +- publié(s) : {{ published }} +- rejeté(s) : {{ rejected }} +- en attente : {{ standby }} + +Nombre de nouveaux abonnés : {{ subscribed }} +Abonnements résiliés : {{ unsubscribed }} + +-- +Stacosys + diff --git a/app/templates/fr/report_message.tpl b/app/templates/fr/report_message.tpl new file mode 100644 index 0000000..45f08e7 --- /dev/null +++ b/app/templates/fr/report_message.tpl @@ -0,0 +1 @@ +Rapport d'activité : {{ site }} \ No newline at end of file From 585300cd4aa0f318b28451b7bfed739c556afce8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 21:21:35 +0200 Subject: [PATCH 083/586] Use absolute URL in emails --- app/services/processor.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 1fb29a4..079b3fa 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -154,7 +154,7 @@ def reply_comment_email(data): comment.url) # notify subscribers every time a new comment is published - notify_subscribed_readers(comment.site.token, comment.url) + notify_subscribed_readers(comment.site.token, comment.site.url, comment.url) def get_email_metadata(message): @@ -196,8 +196,9 @@ def unsubscribe_reader(data): reader.delete_instance() -def notify_subscribed_readers(token, url): +def notify_subscribed_readers(token, site_url, url): logger.info('notify subscribers for %s (%s)' % (url, token)) + article_url = "http://" + site_url + url for reader in Reader.select().join(Site).where(Site.token == token, Reader.url == url): to_email = reader.email @@ -205,15 +206,16 @@ def notify_subscribed_readers(token, url): unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( config.UNSUBSCRIBE_URL, to_email, token, reader.url) email_body = get_template( - 'notify_subscriber').render(article_url=reader.url, + 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) subject = get_template('notify_message').render() mail(to_email, subject, email_body) -def notify_reader(from_email, to_email, token, url): +def notify_reader(from_email, to_email, token, site_url, url): logger.info('notify reader: email %s about URL %s' % (to_email, url)) - email_body = get_template('notify_reader').render(article_url=url) + article_url = "http://" + site_url + url + email_body = get_template('notify_reader').render(article_url=article_url) subject = get_template('notify_message').render() mail(to_email, subject, email_body) From 5c934e51fdfaed217429f6c1d6948317b2c79b9d Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 21:21:35 +0200 Subject: [PATCH 084/586] Use absolute URL in emails --- app/services/processor.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 1fb29a4..079b3fa 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -154,7 +154,7 @@ def reply_comment_email(data): comment.url) # notify subscribers every time a new comment is published - notify_subscribed_readers(comment.site.token, comment.url) + notify_subscribed_readers(comment.site.token, comment.site.url, comment.url) def get_email_metadata(message): @@ -196,8 +196,9 @@ def unsubscribe_reader(data): reader.delete_instance() -def notify_subscribed_readers(token, url): +def notify_subscribed_readers(token, site_url, url): logger.info('notify subscribers for %s (%s)' % (url, token)) + article_url = "http://" + site_url + url for reader in Reader.select().join(Site).where(Site.token == token, Reader.url == url): to_email = reader.email @@ -205,15 +206,16 @@ def notify_subscribed_readers(token, url): unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( config.UNSUBSCRIBE_URL, to_email, token, reader.url) email_body = get_template( - 'notify_subscriber').render(article_url=reader.url, + 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) subject = get_template('notify_message').render() mail(to_email, subject, email_body) -def notify_reader(from_email, to_email, token, url): +def notify_reader(from_email, to_email, token, site_url, url): logger.info('notify reader: email %s about URL %s' % (to_email, url)) - email_body = get_template('notify_reader').render(article_url=url) + article_url = "http://" + site_url + url + email_body = get_template('notify_reader').render(article_url=article_url) subject = get_template('notify_message').render() mail(to_email, subject, email_body) From 822fb1460acb551a740979e15026c622d8bbdffa Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 21:27:06 +0200 Subject: [PATCH 085/586] Use absolute URL --- app/services/processor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 079b3fa..414c98e 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -74,6 +74,8 @@ def new_comment(data): content=message, created=created, published=None) comment.save() + article_url = = "http://" + site.url + url + # render email body template comment_list = ( 'author: %s' % author_name, @@ -87,7 +89,7 @@ def new_comment(data): ) comment_text = '\n'.join(comment_list) email_body = get_template('new_comment').render( - url=url, comment=comment_text) + url=article_url, comment=comment_text) # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) From 11ab7b67c3c8213c7fdaae43f14c69256f32158d Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 21:27:06 +0200 Subject: [PATCH 086/586] Use absolute URL --- app/services/processor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 079b3fa..414c98e 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -74,6 +74,8 @@ def new_comment(data): content=message, created=created, published=None) comment.save() + article_url = = "http://" + site.url + url + # render email body template comment_list = ( 'author: %s' % author_name, @@ -87,7 +89,7 @@ def new_comment(data): ) comment_text = '\n'.join(comment_list) email_body = get_template('new_comment').render( - url=url, comment=comment_text) + url=article_url, comment=comment_text) # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) From 53d9a2e7d363576a1882f3e1f50a9d2c18a5ff6a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 21:28:09 +0200 Subject: [PATCH 087/586] Fix typo --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 414c98e..e1a1473 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -74,7 +74,7 @@ def new_comment(data): content=message, created=created, published=None) comment.save() - article_url = = "http://" + site.url + url + article_url = "http://" + site.url + url # render email body template comment_list = ( From fc438a8ed2713ca8356647b969f20e4f7c68e749 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 21:28:09 +0200 Subject: [PATCH 088/586] Fix typo --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 414c98e..e1a1473 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -74,7 +74,7 @@ def new_comment(data): content=message, created=created, published=None) comment.save() - article_url = = "http://" + site.url + url + article_url = "http://" + site.url + url # render email body template comment_list = ( From 069e563bf198156de8edf5b25299e99f4276233f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 21:36:35 +0200 Subject: [PATCH 089/586] Fix typo --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index e1a1473..b4ef549 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -153,10 +153,11 @@ def reply_comment_email(data): reader_email = get_email_metadata(message) if reader_email: notify_reader(from_email, reader_email, comment.site.token, - comment.url) + comment.site.url, comment.url) # notify subscribers every time a new comment is published - notify_subscribed_readers(comment.site.token, comment.site.url, comment.url) + notify_subscribed_readers( + comment.site.token, comment.site.url, comment.url) def get_email_metadata(message): From 0a153e72fb29b8eff54b9755f4afc911079c960b Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 21:36:35 +0200 Subject: [PATCH 090/586] Fix typo --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index e1a1473..b4ef549 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -153,10 +153,11 @@ def reply_comment_email(data): reader_email = get_email_metadata(message) if reader_email: notify_reader(from_email, reader_email, comment.site.token, - comment.url) + comment.site.url, comment.url) # notify subscribers every time a new comment is published - notify_subscribed_readers(comment.site.token, comment.site.url, comment.url) + notify_subscribed_readers( + comment.site.token, comment.site.url, comment.url) def get_email_metadata(message): From 07702cbd0ed823b94d175907b660248596b85f38 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 11 Sep 2015 21:42:50 +0200 Subject: [PATCH 091/586] Unleash report name constraint --- app/models/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/report.py b/app/models/report.py index a72409d..44e8ac0 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -9,7 +9,7 @@ from app.services.database import get_db from app.models.site import Site class Report(Model): - name = CharField(unique=True) + name = CharField() email = CharField() url = CharField() published = BooleanField(default=False) From beb84a70f4e22edf328d0170723fe9ade39263c4 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 11 Sep 2015 21:42:50 +0200 Subject: [PATCH 092/586] Unleash report name constraint --- app/models/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/report.py b/app/models/report.py index a72409d..44e8ac0 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -9,7 +9,7 @@ from app.services.database import get_db from app.models.site import Site class Report(Model): - name = CharField(unique=True) + name = CharField() email = CharField() url = CharField() published = BooleanField(default=False) From 3f246b60379a0bd55478af07271fe76f21b3ec7c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:15:41 +0200 Subject: [PATCH 093/586] Improve reporting --- app/services/processor.py | 66 ++++++++++++++++++++++++++----------- app/templates/en/report.tpl | 43 ++++++++++++++++++++---- app/templates/fr/report.tpl | 43 ++++++++++++++++++++---- config.py | 3 +- 4 files changed, 123 insertions(+), 32 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index b4ef549..0bb134b 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -6,7 +6,8 @@ import re from datetime import datetime from threading import Thread from queue import Queue -from jinja2 import Environment, FileSystemLoader +from jinja2 import Environment +from jinja2 import FileSystemLoader from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader @@ -15,7 +16,6 @@ import requests import json import config - logger = logging.getLogger(__name__) queue = Queue() proc = None @@ -206,8 +206,8 @@ def notify_subscribed_readers(token, site_url, url): Reader.url == url): to_email = reader.email logger.info('notify reader %s' % to_email) - unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( - config.UNSUBSCRIBE_URL, to_email, token, reader.url) + unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( + config.ROOT_URL, to_email, token, reader.url) email_body = get_template( 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) @@ -253,24 +253,52 @@ def report_unsubscribed(comment): def report(token): site = Site.select().where(Site.token == token).get() - c_standby = Comment.select().join(Site).where( - Site.token == token, Comment.published.is_null(True)).count() - c_published = Report.select().join(Site).where( - Site.token == token, Report.published).count() - c_rejected = Report.select().join(Site).where( - Site.token == token, Report.rejected).count() - c_subscribed = Report.select().join(Site).where( - Site.token == token, Report.subscribed).count() - c_unsubscribed = Report.select().join(Site).where( - Site.token == token, Report.unsubscribed).count() - email_body = get_template('report').render(standby=c_standby, published=c_published, rejected=c_rejected, - subscribed=c_subscribed,unsubscribed=c_unsubscribed) + + standbys = [] + for row in Comment.select().join(Site).where( + Site.token == token, Comment.published.is_null(True)): + standbys.append({'url': "http://" + site.url + row.url, + 'created': row.created.strftime('%d/%m/%y %H:%M'), + 'name': row.author_name, 'content': row.content, + 'id': row.id}) + + published = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.published): + published.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + rejected = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.rejected): + rejected.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + subscribed = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.subscribed): + subscribed.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + unsubscribed = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.subscribed): + unsubscribed.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + email_body = get_template('report').render(secret=config.SECRET, + root_url=config.ROOT_URL, + standbys=standbys, + published=published, + rejected=rejected, + subscribed=subscribed, + unsubscribed=unsubscribed) subject = get_template('report_message').render(site=site.name) - mail(site.admin_email, subject, email_body) + print(email_body) + #mail(site.admin_email, subject, email_body) # TODO: delete report table - #Report.delete().execute() - + # Report.delete().execute() def mail(to_email, subject, message): diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl index 188edb8..0960555 100644 --- a/app/templates/en/report.tpl +++ b/app/templates/en/report.tpl @@ -1,11 +1,42 @@ -Comments : -- published : {{ published }} -- rejected : {{ rejected }} -- standby : {{ standby }} +{% if subscribed %} +{% if subscribed|length > 1 %}NEW SUBSCRIPTIONS{% else %}NEW SUBSCRIPTION{% endif %} : +{% for c in subscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} -New readers : {{ subscribed }} -Cancelled subscriptions : {{ unsubscribed }} +{% endif %} +{% if unsubscribed %} +{% if unsubscribed|length > 1 %}CANCELLED SUBSCRIPTIONS{% else %}CANCELLED SUBSCRIPTION{% endif %} : +{% for c in unsubscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} +{% endif %} +{% if published %} +{% if published|length > 1 %}PUBLISHED COMMENTS{% else %}PUBLISHED COMMENT{% endif %} : +{% for c in published %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if rejected %} +{% if rejected|length > 1 %}REJECTED COMMENTS{% else %}REJECTED COMMENT{% endif %} : +{% for c in rejected %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if standbys %} +{% if standbys|length > 1 %}STANDBY COMMENTS{% else %}STANDBY COMMENT{% endif %} : +{% for c in standbys %} +- {{ c.name }} ({{ c.created }}) => {{ c.url }} +{{ c.content }} + + Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} + Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} + +{% endfor %} +{% endif %} -- Stacosys diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl index 0445295..9c8f52b 100644 --- a/app/templates/fr/report.tpl +++ b/app/templates/fr/report.tpl @@ -1,11 +1,42 @@ -Nombre de commentaires : -- publié(s) : {{ published }} -- rejeté(s) : {{ rejected }} -- en attente : {{ standby }} +{% if subscribed %} +{% if subscribed|length > 1 %}NOUVEAUX ABONNEMENTS{% else %}NOUVEL ABONNEMENT{% endif %} : +{% for c in subscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} -Nombre de nouveaux abonnés : {{ subscribed }} -Abonnements résiliés : {{ unsubscribed }} +{% endif %} +{% if unsubscribed %} +{% if unsubscribed|length > 1 %}ABONNEMENTS RESILIES{% else %}ABONNEMENT RESILIE{% endif %} : +{% for c in unsubscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} +{% endif %} +{% if published %} +{% if published|length > 1 %}COMMENTAIRES PUBLIES{% else %}COMMENTAIRE PUBLIE{% endif %} : +{% for c in published %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if rejected %} +{% if rejected|length > 1 %}COMMENTAIRES REJETES{% else %}COMMENTAIRE REJETE{% endif %} : +{% for c in rejected %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if standbys %} +{% if standbys|length > 1 %}COMMENTAIRES EN ATTENTE{% else %}COMMENTAIRE EN ATTENTE{% endif %} : +{% for c in standbys %} +- {{ c.name }} ({{ c.created }}) => {{ c.url }} +{{ c.content }} + + Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} + Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} + +{% endfor %} +{% endif %} -- Stacosys diff --git a/config.py b/config.py index 7e5298c..11ef9e1 100644 --- a/config.py +++ b/config.py @@ -16,4 +16,5 @@ SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" SECRET = "Uqca5Kc8xuU6THz9" -UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' +ROOT_URL = 'http://localhost:8000' + From d40cef9a25f62dc72cbddea689f6b58e0f81c12b Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:15:41 +0200 Subject: [PATCH 094/586] Improve reporting --- app/services/processor.py | 66 ++++++++++++++++++++++++++----------- app/templates/en/report.tpl | 43 ++++++++++++++++++++---- app/templates/fr/report.tpl | 43 ++++++++++++++++++++---- config.py | 3 +- 4 files changed, 123 insertions(+), 32 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index b4ef549..0bb134b 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -6,7 +6,8 @@ import re from datetime import datetime from threading import Thread from queue import Queue -from jinja2 import Environment, FileSystemLoader +from jinja2 import Environment +from jinja2 import FileSystemLoader from app.models.site import Site from app.models.comment import Comment from app.models.reader import Reader @@ -15,7 +16,6 @@ import requests import json import config - logger = logging.getLogger(__name__) queue = Queue() proc = None @@ -206,8 +206,8 @@ def notify_subscribed_readers(token, site_url, url): Reader.url == url): to_email = reader.email logger.info('notify reader %s' % to_email) - unsubscribe_url = '%s?email=%s&token=%s&url=%s' % ( - config.UNSUBSCRIBE_URL, to_email, token, reader.url) + unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( + config.ROOT_URL, to_email, token, reader.url) email_body = get_template( 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) @@ -253,24 +253,52 @@ def report_unsubscribed(comment): def report(token): site = Site.select().where(Site.token == token).get() - c_standby = Comment.select().join(Site).where( - Site.token == token, Comment.published.is_null(True)).count() - c_published = Report.select().join(Site).where( - Site.token == token, Report.published).count() - c_rejected = Report.select().join(Site).where( - Site.token == token, Report.rejected).count() - c_subscribed = Report.select().join(Site).where( - Site.token == token, Report.subscribed).count() - c_unsubscribed = Report.select().join(Site).where( - Site.token == token, Report.unsubscribed).count() - email_body = get_template('report').render(standby=c_standby, published=c_published, rejected=c_rejected, - subscribed=c_subscribed,unsubscribed=c_unsubscribed) + + standbys = [] + for row in Comment.select().join(Site).where( + Site.token == token, Comment.published.is_null(True)): + standbys.append({'url': "http://" + site.url + row.url, + 'created': row.created.strftime('%d/%m/%y %H:%M'), + 'name': row.author_name, 'content': row.content, + 'id': row.id}) + + published = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.published): + published.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + rejected = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.rejected): + rejected.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + subscribed = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.subscribed): + subscribed.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + unsubscribed = [] + for row in Report.select().join(Site).where( + Site.token == token, Report.subscribed): + unsubscribed.append({'url': "http://" + site.url + row.url, + 'name': row.name, 'email': row.email}) + + email_body = get_template('report').render(secret=config.SECRET, + root_url=config.ROOT_URL, + standbys=standbys, + published=published, + rejected=rejected, + subscribed=subscribed, + unsubscribed=unsubscribed) subject = get_template('report_message').render(site=site.name) - mail(site.admin_email, subject, email_body) + print(email_body) + #mail(site.admin_email, subject, email_body) # TODO: delete report table - #Report.delete().execute() - + # Report.delete().execute() def mail(to_email, subject, message): diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl index 188edb8..0960555 100644 --- a/app/templates/en/report.tpl +++ b/app/templates/en/report.tpl @@ -1,11 +1,42 @@ -Comments : -- published : {{ published }} -- rejected : {{ rejected }} -- standby : {{ standby }} +{% if subscribed %} +{% if subscribed|length > 1 %}NEW SUBSCRIPTIONS{% else %}NEW SUBSCRIPTION{% endif %} : +{% for c in subscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} -New readers : {{ subscribed }} -Cancelled subscriptions : {{ unsubscribed }} +{% endif %} +{% if unsubscribed %} +{% if unsubscribed|length > 1 %}CANCELLED SUBSCRIPTIONS{% else %}CANCELLED SUBSCRIPTION{% endif %} : +{% for c in unsubscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} +{% endif %} +{% if published %} +{% if published|length > 1 %}PUBLISHED COMMENTS{% else %}PUBLISHED COMMENT{% endif %} : +{% for c in published %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if rejected %} +{% if rejected|length > 1 %}REJECTED COMMENTS{% else %}REJECTED COMMENT{% endif %} : +{% for c in rejected %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if standbys %} +{% if standbys|length > 1 %}STANDBY COMMENTS{% else %}STANDBY COMMENT{% endif %} : +{% for c in standbys %} +- {{ c.name }} ({{ c.created }}) => {{ c.url }} +{{ c.content }} + + Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} + Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} + +{% endfor %} +{% endif %} -- Stacosys diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl index 0445295..9c8f52b 100644 --- a/app/templates/fr/report.tpl +++ b/app/templates/fr/report.tpl @@ -1,11 +1,42 @@ -Nombre de commentaires : -- publié(s) : {{ published }} -- rejeté(s) : {{ rejected }} -- en attente : {{ standby }} +{% if subscribed %} +{% if subscribed|length > 1 %}NOUVEAUX ABONNEMENTS{% else %}NOUVEL ABONNEMENT{% endif %} : +{% for c in subscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} -Nombre de nouveaux abonnés : {{ subscribed }} -Abonnements résiliés : {{ unsubscribed }} +{% endif %} +{% if unsubscribed %} +{% if unsubscribed|length > 1 %}ABONNEMENTS RESILIES{% else %}ABONNEMENT RESILIE{% endif %} : +{% for c in unsubscribed %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} +{% endif %} +{% if published %} +{% if published|length > 1 %}COMMENTAIRES PUBLIES{% else %}COMMENTAIRE PUBLIE{% endif %} : +{% for c in published %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if rejected %} +{% if rejected|length > 1 %}COMMENTAIRES REJETES{% else %}COMMENTAIRE REJETE{% endif %} : +{% for c in rejected %} +- {{ c.name }} ({{ c.email }}) => {{ c.url }} +{% endfor %} + +{% endif %} +{% if standbys %} +{% if standbys|length > 1 %}COMMENTAIRES EN ATTENTE{% else %}COMMENTAIRE EN ATTENTE{% endif %} : +{% for c in standbys %} +- {{ c.name }} ({{ c.created }}) => {{ c.url }} +{{ c.content }} + + Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} + Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} + +{% endfor %} +{% endif %} -- Stacosys diff --git a/config.py b/config.py index 7e5298c..11ef9e1 100644 --- a/config.py +++ b/config.py @@ -16,4 +16,5 @@ SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" SECRET = "Uqca5Kc8xuU6THz9" -UNSUBSCRIBE_URL = 'http://localhost:8000/unsubscribe' +ROOT_URL = 'http://localhost:8000' + From 917ddf26ba7837b90daa7f6f0f2d459c257f3882 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:20:28 +0200 Subject: [PATCH 095/586] Enable email sending --- app/services/processor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 0bb134b..06ba4bc 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -294,8 +294,8 @@ def report(token): subscribed=subscribed, unsubscribed=unsubscribed) subject = get_template('report_message').render(site=site.name) - print(email_body) - #mail(site.admin_email, subject, email_body) + + mail(site.admin_email, subject, email_body) # TODO: delete report table # Report.delete().execute() From c149a44d6d3a5039863145049d619f665db47b01 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:20:28 +0200 Subject: [PATCH 096/586] Enable email sending --- app/services/processor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 0bb134b..06ba4bc 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -294,8 +294,8 @@ def report(token): subscribed=subscribed, unsubscribed=unsubscribed) subject = get_template('report_message').render(site=site.name) - print(email_body) - #mail(site.admin_email, subject, email_body) + + mail(site.admin_email, subject, email_body) # TODO: delete report table # Report.delete().execute() From f7093cb029e7ee884619fe61aa4806f9dc7c8682 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:33:42 +0200 Subject: [PATCH 097/586] Accept / reject comments via API --- app/controllers/api.py | 44 ++++++++++++++++++++++++++++++++++++++- app/services/processor.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 1389155..5400d4d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -109,8 +109,50 @@ def report(): processor.enqueue({'request': 'report', 'data': token}) + except: logger.exception("report failure") abort(500) - return "OK" \ No newline at end of file + return "OK" + + +@app.route("/accept", methods=['GET']) +def accept_comment(): + + try: + id = request.args.get('id', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_accept', 'data': id}) + + except: + logger.exception("accept failure") + abort(500) + + return "PUBLISHED" + + +@app.route("/reject", methods=['GET']) +def reject_comment(): + + try: + id = request.args.get('id', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_reject', 'data': id}) + + except: + logger.exception("reject failure") + abort(500) + + return "REJECTED" + diff --git a/app/services/processor.py b/app/services/processor.py index 06ba4bc..11b8752 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -42,6 +42,10 @@ class Processor(Thread): unsubscribe_reader(msg['data']) elif msg['request'] == 'report': report(msg['data']) + elif msg['request'] == 'late_accept': + late_accept_comment(msg['data']) + elif msg['request'] == 'late_reject': + late_reject_comment(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -160,6 +164,35 @@ def reply_comment_email(data): comment.site.token, comment.site.url, comment.url) +def late_reject_comment(id): + + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == id).get() + + # report event + report_rejected(comment) + + # delete Comment row + comment.delete_instance() + + logger.info('late reject comment: %d' % id) + + +def late_accept_comment(id): + + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == id).get() + + # report event + report_published(comment) + + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + + logger.info('late accept comment: %d' % id) + + def get_email_metadata(message): # retrieve metadata reader email from email body sent by admin email = "" From 4b71710dc843357f042ca69a0c72fd794052d817 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:33:42 +0200 Subject: [PATCH 098/586] Accept / reject comments via API --- app/controllers/api.py | 44 ++++++++++++++++++++++++++++++++++++++- app/services/processor.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 1389155..5400d4d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -109,8 +109,50 @@ def report(): processor.enqueue({'request': 'report', 'data': token}) + except: logger.exception("report failure") abort(500) - return "OK" \ No newline at end of file + return "OK" + + +@app.route("/accept", methods=['GET']) +def accept_comment(): + + try: + id = request.args.get('id', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_accept', 'data': id}) + + except: + logger.exception("accept failure") + abort(500) + + return "PUBLISHED" + + +@app.route("/reject", methods=['GET']) +def reject_comment(): + + try: + id = request.args.get('id', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_reject', 'data': id}) + + except: + logger.exception("reject failure") + abort(500) + + return "REJECTED" + diff --git a/app/services/processor.py b/app/services/processor.py index 06ba4bc..11b8752 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -42,6 +42,10 @@ class Processor(Thread): unsubscribe_reader(msg['data']) elif msg['request'] == 'report': report(msg['data']) + elif msg['request'] == 'late_accept': + late_accept_comment(msg['data']) + elif msg['request'] == 'late_reject': + late_reject_comment(msg['data']) else: logger.info("throw unknown request " + str(msg)) except: @@ -160,6 +164,35 @@ def reply_comment_email(data): comment.site.token, comment.site.url, comment.url) +def late_reject_comment(id): + + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == id).get() + + # report event + report_rejected(comment) + + # delete Comment row + comment.delete_instance() + + logger.info('late reject comment: %d' % id) + + +def late_accept_comment(id): + + # retrieve site and comment rows + comment = Comment.select().where(Comment.id == id).get() + + # report event + report_published(comment) + + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + + logger.info('late accept comment: %d' % id) + + def get_email_metadata(message): # retrieve metadata reader email from email body sent by admin email = "" From 5f97260b7be0edf325106224a603b5640b30bcda Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:36:06 +0200 Subject: [PATCH 099/586] Fix QP retrieval --- app/controllers/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5400d4d..6f0bca2 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -121,7 +121,7 @@ def report(): def accept_comment(): try: - id = request.args.get('id', '') + id = request.args.get('comment', 0) secret = request.args.get('secret', '') if secret != config.SECRET: @@ -141,7 +141,7 @@ def accept_comment(): def reject_comment(): try: - id = request.args.get('id', '') + id = request.args.get('comment', 0) secret = request.args.get('secret', '') if secret != config.SECRET: From b9722ffd47274d1e0db70ca202000dea180bcff3 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:36:06 +0200 Subject: [PATCH 100/586] Fix QP retrieval --- app/controllers/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5400d4d..6f0bca2 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -121,7 +121,7 @@ def report(): def accept_comment(): try: - id = request.args.get('id', '') + id = request.args.get('comment', 0) secret = request.args.get('secret', '') if secret != config.SECRET: @@ -141,7 +141,7 @@ def accept_comment(): def reject_comment(): try: - id = request.args.get('id', '') + id = request.args.get('comment', 0) secret = request.args.get('secret', '') if secret != config.SECRET: From c461c5fa4906c27ecefe828a1b9e5a93cbd65de6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:39:09 +0200 Subject: [PATCH 101/586] Fix log format --- app/controllers/api.py | 4 ++-- app/services/processor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 6f0bca2..9928afd 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -121,7 +121,7 @@ def report(): def accept_comment(): try: - id = request.args.get('comment', 0) + id = request.args.get('comment', '') secret = request.args.get('secret', '') if secret != config.SECRET: @@ -141,7 +141,7 @@ def accept_comment(): def reject_comment(): try: - id = request.args.get('comment', 0) + id = request.args.get('comment', '') secret = request.args.get('secret', '') if secret != config.SECRET: diff --git a/app/services/processor.py b/app/services/processor.py index 11b8752..b546d9a 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -175,7 +175,7 @@ def late_reject_comment(id): # delete Comment row comment.delete_instance() - logger.info('late reject comment: %d' % id) + logger.info('late reject comment: %s' % id) def late_accept_comment(id): @@ -190,7 +190,7 @@ def late_accept_comment(id): comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('late accept comment: %d' % id) + logger.info('late accept comment: %s' % id) def get_email_metadata(message): From 2b08f01e8fd232df02f8603d9101f17018ea7be3 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:39:09 +0200 Subject: [PATCH 102/586] Fix log format --- app/controllers/api.py | 4 ++-- app/services/processor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 6f0bca2..9928afd 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -121,7 +121,7 @@ def report(): def accept_comment(): try: - id = request.args.get('comment', 0) + id = request.args.get('comment', '') secret = request.args.get('secret', '') if secret != config.SECRET: @@ -141,7 +141,7 @@ def accept_comment(): def reject_comment(): try: - id = request.args.get('comment', 0) + id = request.args.get('comment', '') secret = request.args.get('secret', '') if secret != config.SECRET: diff --git a/app/services/processor.py b/app/services/processor.py index 11b8752..b546d9a 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -175,7 +175,7 @@ def late_reject_comment(id): # delete Comment row comment.delete_instance() - logger.info('late reject comment: %d' % id) + logger.info('late reject comment: %s' % id) def late_accept_comment(id): @@ -190,7 +190,7 @@ def late_accept_comment(id): comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('late accept comment: %d' % id) + logger.info('late accept comment: %s' % id) def get_email_metadata(message): From e59c956d0d1e845bde8ee95bfdc2209044f15fa8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Sep 2015 12:42:15 +0200 Subject: [PATCH 103/586] Delete report data once generated --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index b546d9a..6b3ec75 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -330,8 +330,9 @@ def report(token): mail(site.admin_email, subject, email_body) - # TODO: delete report table - # Report.delete().execute() + # delete report table + Report.delete().execute() + def mail(to_email, subject, message): From 08bd67f17fd9250a65d4d63dac3d290884a9a790 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 20 Sep 2015 12:42:15 +0200 Subject: [PATCH 104/586] Delete report data once generated --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index b546d9a..6b3ec75 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -330,8 +330,9 @@ def report(token): mail(site.admin_email, subject, email_body) - # TODO: delete report table - # Report.delete().execute() + # delete report table + Report.delete().execute() + def mail(to_email, subject, message): From 43220ea750787da268e9bcd4a19f7aed21296436 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 26 Sep 2015 16:05:34 +0200 Subject: [PATCH 105/586] Fix typo --- app/templates/en/new_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index 3e176e4..490f714 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -1,6 +1,6 @@ Hi, -A new comment has been submitted for post {{ url }}. +A new comment has been submitted for post {{ url }} You have two choices: - reject the comment by replying NO (or no), diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index ae84899..5671563 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -1,6 +1,6 @@ Bonjour, -Un nouveau commentaire a été posté pour l'article {{ url }}. +Un nouveau commentaire a été posté pour l'article {{ url }} Vous avez deux réponses possibles : - rejeter le commentaire en répondant NO (ou no), From aa11b1f1c2fa6dbc28a09e9fb97ab7fb41358dd5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 26 Sep 2015 16:05:34 +0200 Subject: [PATCH 106/586] Fix typo --- app/templates/en/new_comment.tpl | 2 +- app/templates/fr/new_comment.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/new_comment.tpl b/app/templates/en/new_comment.tpl index 3e176e4..490f714 100644 --- a/app/templates/en/new_comment.tpl +++ b/app/templates/en/new_comment.tpl @@ -1,6 +1,6 @@ Hi, -A new comment has been submitted for post {{ url }}. +A new comment has been submitted for post {{ url }} You have two choices: - reject the comment by replying NO (or no), diff --git a/app/templates/fr/new_comment.tpl b/app/templates/fr/new_comment.tpl index ae84899..5671563 100644 --- a/app/templates/fr/new_comment.tpl +++ b/app/templates/fr/new_comment.tpl @@ -1,6 +1,6 @@ Bonjour, -Un nouveau commentaire a été posté pour l'article {{ url }}. +Un nouveau commentaire a été posté pour l'article {{ url }} Vous avez deux réponses possibles : - rejeter le commentaire en répondant NO (ou no), From c88d20b71018b548a787468f0c9f758b0747cee9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Sep 2015 12:04:29 +0200 Subject: [PATCH 107/586] Use PyRSS2Gen --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index c3ca49c..dc6b206 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,8 @@ Jinja2==2.7.3 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 +PyRSS2Gen==1.1 requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 +wheel==0.24.0 From 6237b30e6f8d583a8c0a2f693935b8b064b985e0 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 27 Sep 2015 12:04:29 +0200 Subject: [PATCH 108/586] Use PyRSS2Gen --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index c3ca49c..dc6b206 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,8 @@ Jinja2==2.7.3 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 +PyRSS2Gen==1.1 requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 +wheel==0.24.0 From 5c68eff22b2c73f8b985719cf3dac40cbffe1dd6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Sep 2015 19:40:40 +0200 Subject: [PATCH 109/586] Generate RSS for comments --- app/services/processor.py | 49 +++++++++++++++++++++----- app/templates/en/rss_title_message.tpl | 1 + app/templates/fr/rss_title_message.tpl | 1 + config.py | 1 + requirements.txt | 1 + 5 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 app/templates/en/rss_title_message.tpl create mode 100644 app/templates/fr/rss_title_message.tpl diff --git a/app/services/processor.py b/app/services/processor.py index 6b3ec75..e82fa3e 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -15,6 +15,8 @@ from app.models.report import Report import requests import json import config +import PyRSS2Gen +import markdown logger = logging.getLogger(__name__) queue = Queue() @@ -146,9 +148,11 @@ def reply_comment_email(data): # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('commit comment: %d' % comment_id) + # rebuild RSS + rss(token) + # send approval confirmation email to admin email_body = get_template('approve_comment').render(original=message) mail(from_email, 'Re: ' + subject, email_body) @@ -297,27 +301,27 @@ def report(token): published = [] for row in Report.select().join(Site).where( - Site.token == token, Report.published): + Site.token == token, Report.published): published.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) rejected = [] for row in Report.select().join(Site).where( - Site.token == token, Report.rejected): + Site.token == token, Report.rejected): rejected.append({'url': "http://" + site.url + row.url, 'name': row.name, 'email': row.email}) subscribed = [] for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): + Site.token == token, Report.subscribed): subscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) unsubscribed = [] for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): + Site.token == token, Report.subscribed): unsubscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) email_body = get_template('report').render(secret=config.SECRET, root_url=config.ROOT_URL, @@ -334,6 +338,31 @@ def report(token): Report.delete().execute() +def rss(token): + site = Site.select().where(Site.token == token).get() + rss_title = get_template('rss_title_message').render(site=site.name) + md = markdown.Markdown() + + items = [] + for row in Comment.select().join(Site).where( + Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + items.append(PyRSS2Gen.RSSItem( + title='%s - http://%s%s' % (row.author_name, site.url, row.url), + link="http://%s%s" % (site.url, row.url), + description=md.convert(row.content), + guid=PyRSS2Gen.Guid('%s%d' % (token, row.id)), + pubDate=row.published + )) + + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="http://" + site.url, + description="Commentaires du site '%s'" % site.name, + lastBuildDate=datetime.now(), + items=items) + rss.write_xml(open(config.RSS_FILE, "w"), encoding="utf-8") + + def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} @@ -368,6 +397,10 @@ def start(template_dir): logger.info("load templates from directory %s" % template_dir) env = Environment(loader=FileSystemLoader(template_dir)) + # generate RSS for all sites + for site in Site.select(): + rss(site.token) + # start processor thread proc = Processor() proc.start() diff --git a/app/templates/en/rss_title_message.tpl b/app/templates/en/rss_title_message.tpl new file mode 100644 index 0000000..c6c6d04 --- /dev/null +++ b/app/templates/en/rss_title_message.tpl @@ -0,0 +1 @@ +Comments : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/rss_title_message.tpl b/app/templates/fr/rss_title_message.tpl new file mode 100644 index 0000000..34cc049 --- /dev/null +++ b/app/templates/fr/rss_title_message.tpl @@ -0,0 +1 @@ +Commentaires du site : {{ site }} \ No newline at end of file diff --git a/config.py b/config.py index 11ef9e1..5dd761a 100644 --- a/config.py +++ b/config.py @@ -18,3 +18,4 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' +RSS_FILE = 'comments.xml' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dc6b206..4b7c647 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ Flask==0.10.1 Flask-Cors==2.0.1 itsdangerous==0.24 Jinja2==2.7.3 +Markdown==2.6.2 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 From 37095bbe79bdaeecfff5637416db37e09d7836d4 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 27 Sep 2015 19:40:40 +0200 Subject: [PATCH 110/586] Generate RSS for comments --- app/services/processor.py | 49 +++++++++++++++++++++----- app/templates/en/rss_title_message.tpl | 1 + app/templates/fr/rss_title_message.tpl | 1 + config.py | 1 + requirements.txt | 1 + 5 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 app/templates/en/rss_title_message.tpl create mode 100644 app/templates/fr/rss_title_message.tpl diff --git a/app/services/processor.py b/app/services/processor.py index 6b3ec75..e82fa3e 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -15,6 +15,8 @@ from app.models.report import Report import requests import json import config +import PyRSS2Gen +import markdown logger = logging.getLogger(__name__) queue = Queue() @@ -146,9 +148,11 @@ def reply_comment_email(data): # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('commit comment: %d' % comment_id) + # rebuild RSS + rss(token) + # send approval confirmation email to admin email_body = get_template('approve_comment').render(original=message) mail(from_email, 'Re: ' + subject, email_body) @@ -297,27 +301,27 @@ def report(token): published = [] for row in Report.select().join(Site).where( - Site.token == token, Report.published): + Site.token == token, Report.published): published.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) rejected = [] for row in Report.select().join(Site).where( - Site.token == token, Report.rejected): + Site.token == token, Report.rejected): rejected.append({'url': "http://" + site.url + row.url, 'name': row.name, 'email': row.email}) subscribed = [] for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): + Site.token == token, Report.subscribed): subscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) unsubscribed = [] for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): + Site.token == token, Report.subscribed): unsubscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) + 'name': row.name, 'email': row.email}) email_body = get_template('report').render(secret=config.SECRET, root_url=config.ROOT_URL, @@ -334,6 +338,31 @@ def report(token): Report.delete().execute() +def rss(token): + site = Site.select().where(Site.token == token).get() + rss_title = get_template('rss_title_message').render(site=site.name) + md = markdown.Markdown() + + items = [] + for row in Comment.select().join(Site).where( + Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + items.append(PyRSS2Gen.RSSItem( + title='%s - http://%s%s' % (row.author_name, site.url, row.url), + link="http://%s%s" % (site.url, row.url), + description=md.convert(row.content), + guid=PyRSS2Gen.Guid('%s%d' % (token, row.id)), + pubDate=row.published + )) + + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="http://" + site.url, + description="Commentaires du site '%s'" % site.name, + lastBuildDate=datetime.now(), + items=items) + rss.write_xml(open(config.RSS_FILE, "w"), encoding="utf-8") + + def mail(to_email, subject, message): headers = {'Content-Type': 'application/json; charset=utf-8'} @@ -368,6 +397,10 @@ def start(template_dir): logger.info("load templates from directory %s" % template_dir) env = Environment(loader=FileSystemLoader(template_dir)) + # generate RSS for all sites + for site in Site.select(): + rss(site.token) + # start processor thread proc = Processor() proc.start() diff --git a/app/templates/en/rss_title_message.tpl b/app/templates/en/rss_title_message.tpl new file mode 100644 index 0000000..c6c6d04 --- /dev/null +++ b/app/templates/en/rss_title_message.tpl @@ -0,0 +1 @@ +Comments : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/rss_title_message.tpl b/app/templates/fr/rss_title_message.tpl new file mode 100644 index 0000000..34cc049 --- /dev/null +++ b/app/templates/fr/rss_title_message.tpl @@ -0,0 +1 @@ +Commentaires du site : {{ site }} \ No newline at end of file diff --git a/config.py b/config.py index 11ef9e1..5dd761a 100644 --- a/config.py +++ b/config.py @@ -18,3 +18,4 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' +RSS_FILE = 'comments.xml' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dc6b206..4b7c647 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ Flask==0.10.1 Flask-Cors==2.0.1 itsdangerous==0.24 Jinja2==2.7.3 +Markdown==2.6.2 MarkupSafe==0.23 peewee==2.6.0 PyMySQL==0.6.6 From cf5ea909b6a766099d57c8a43bb01b35259fce64 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Sep 2015 20:01:49 +0200 Subject: [PATCH 111/586] Enhance gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6328437..40cc63e 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ target/ myconfig.json db.sqlite node_modules +comments.xml From 90a117ef9a334bac3d1ea60c8e6f492ee18ce33f Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 27 Sep 2015 20:01:49 +0200 Subject: [PATCH 112/586] Enhance gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6328437..40cc63e 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ target/ myconfig.json db.sqlite node_modules +comments.xml From dbe2a83914195fe03f360d24872c6ceb9b71d88c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 28 Sep 2015 09:11:28 +0200 Subject: [PATCH 113/586] Change RSS title --- app/templates/en/rss_title_message.tpl | 2 +- app/templates/fr/rss_title_message.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/rss_title_message.tpl b/app/templates/en/rss_title_message.tpl index c6c6d04..b0b1e30 100644 --- a/app/templates/en/rss_title_message.tpl +++ b/app/templates/en/rss_title_message.tpl @@ -1 +1 @@ -Comments : {{ site }} \ No newline at end of file +{{ site }} : comments diff --git a/app/templates/fr/rss_title_message.tpl b/app/templates/fr/rss_title_message.tpl index 34cc049..db993f6 100644 --- a/app/templates/fr/rss_title_message.tpl +++ b/app/templates/fr/rss_title_message.tpl @@ -1 +1 @@ -Commentaires du site : {{ site }} \ No newline at end of file +{{ site }} : commentaires From 0ef22805641c994c6fd1c7ed3cd32bb797915e36 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 28 Sep 2015 09:11:28 +0200 Subject: [PATCH 114/586] Change RSS title --- app/templates/en/rss_title_message.tpl | 2 +- app/templates/fr/rss_title_message.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/en/rss_title_message.tpl b/app/templates/en/rss_title_message.tpl index c6c6d04..b0b1e30 100644 --- a/app/templates/en/rss_title_message.tpl +++ b/app/templates/en/rss_title_message.tpl @@ -1 +1 @@ -Comments : {{ site }} \ No newline at end of file +{{ site }} : comments diff --git a/app/templates/fr/rss_title_message.tpl b/app/templates/fr/rss_title_message.tpl index 34cc049..db993f6 100644 --- a/app/templates/fr/rss_title_message.tpl +++ b/app/templates/fr/rss_title_message.tpl @@ -1 +1 @@ -Commentaires du site : {{ site }} \ No newline at end of file +{{ site }} : commentaires From 2782301cdb16e64be3e080f3b811fcee05d73c9a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 5 Oct 2015 16:40:39 +0200 Subject: [PATCH 115/586] Fix RSS links --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index e82fa3e..f35440c 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -346,11 +346,12 @@ def rss(token): items = [] for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + item_link = "http://%s%s" % (site.url, row.url) items.append(PyRSS2Gen.RSSItem( title='%s - http://%s%s' % (row.author_name, site.url, row.url), - link="http://%s%s" % (site.url, row.url), + link= item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s%d' % (token, row.id)), + guid=PyRSS2Gen.Guid(item_link), pubDate=row.published )) From 167c84645bcdace63146d62a15be9c7bd9af3a52 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 5 Oct 2015 16:40:39 +0200 Subject: [PATCH 116/586] Fix RSS links --- app/services/processor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index e82fa3e..f35440c 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -346,11 +346,12 @@ def rss(token): items = [] for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + item_link = "http://%s%s" % (site.url, row.url) items.append(PyRSS2Gen.RSSItem( title='%s - http://%s%s' % (row.author_name, site.url, row.url), - link="http://%s%s" % (site.url, row.url), + link= item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s%d' % (token, row.id)), + guid=PyRSS2Gen.Guid(item_link), pubDate=row.published )) From 7a698d16a9e687e778dfb5bfcb90c37cc21a2199 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 9 Oct 2015 20:45:44 +0200 Subject: [PATCH 117/586] Fix report --- app/services/processor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index f35440c..3d522c4 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -274,16 +274,16 @@ def report_published(comment): report.save() -def report_subscribed(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, +def report_subscribed(reader): + report = Report(site=reader.site, url=reader.url, + name='', email=reader.email, subscribed=True) report.save() -def report_unsubscribed(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, +def report_unsubscribed(reader): + report = Report(site=reader.site, url=reader.url, + name='', email=reader.email, unsubscribed=True) report.save() From 5c894d7957a86c1ee8fd225a5566aa88c40c7abd Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 9 Oct 2015 20:45:44 +0200 Subject: [PATCH 118/586] Fix report --- app/services/processor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index f35440c..3d522c4 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -274,16 +274,16 @@ def report_published(comment): report.save() -def report_subscribed(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, +def report_subscribed(reader): + report = Report(site=reader.site, url=reader.url, + name='', email=reader.email, subscribed=True) report.save() -def report_unsubscribed(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, +def report_unsubscribed(reader): + report = Report(site=reader.site, url=reader.url, + name='', email=reader.email, unsubscribed=True) report.save() From 09b834d7a009fb36da874d543ec1e87d51929a6f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 25 Oct 2015 14:40:24 +0100 Subject: [PATCH 119/586] Update README.md --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 352891d..b24eb9c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ -# stacosys -Static sites need a Commenting System +## Stacosys + +Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. + +Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an +universal way to discuss. You can answer from any device using an e-mail client or a Webmail. + +### Features overview + +Stacosys main feature is comment management. + +Here is the workflow: + +- Readers submit comments via a comment form embedded in blog pages +- Blog administrator receives an email notification from Stacosys when a + comment is submitted +- Blog administrator can approve or drop the comment by replying to e-mail +- Stacosys stores approved comment in its database. + +Moreover Stacosys has an additional feature: readers can subscribe to further +comments for a post if they have provided an email. + +Stacosys is localized (english and french). + +### Technically speaking, how does it work? + +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using [CORS](http://enable-cors.org) requests. Each blog has a unique ID. Thus Stacosys can serve multiple blogs. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. + +### FAQ + +*So the blog needs a server-side language?* +- Right! Stacosys is written in Python and it uses Flask Web framework. You + keeps on serving static pages for the blog but you have to link two URL with + Pecosys. If you use NginX or Apache2 (with Proxy modules), it's not a big + deal. + +*How do you block spammers?* +- That's a huge topic. Current comment form is basic: no captcha support but a honey + pot. Nothing prevents from improving the template with JavaScript libs to do more + complex things. + +*Which database is used?* +- Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. + +*Which technologies are used?* + +- [Python](https://www.python.org) +- [Flask](http://flask.pocoo.org) +- [Peewee ORM](http://docs.peewee-orm.com) +- [Markdown](http://daringfireball.net/projects/markdown) + From 41911a7589488a1619e0e11f3956c3029db57563 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 25 Oct 2015 14:40:24 +0100 Subject: [PATCH 120/586] Update README.md --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 352891d..b24eb9c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ -# stacosys -Static sites need a Commenting System +## Stacosys + +Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. + +Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an +universal way to discuss. You can answer from any device using an e-mail client or a Webmail. + +### Features overview + +Stacosys main feature is comment management. + +Here is the workflow: + +- Readers submit comments via a comment form embedded in blog pages +- Blog administrator receives an email notification from Stacosys when a + comment is submitted +- Blog administrator can approve or drop the comment by replying to e-mail +- Stacosys stores approved comment in its database. + +Moreover Stacosys has an additional feature: readers can subscribe to further +comments for a post if they have provided an email. + +Stacosys is localized (english and french). + +### Technically speaking, how does it work? + +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using [CORS](http://enable-cors.org) requests. Each blog has a unique ID. Thus Stacosys can serve multiple blogs. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. + +### FAQ + +*So the blog needs a server-side language?* +- Right! Stacosys is written in Python and it uses Flask Web framework. You + keeps on serving static pages for the blog but you have to link two URL with + Pecosys. If you use NginX or Apache2 (with Proxy modules), it's not a big + deal. + +*How do you block spammers?* +- That's a huge topic. Current comment form is basic: no captcha support but a honey + pot. Nothing prevents from improving the template with JavaScript libs to do more + complex things. + +*Which database is used?* +- Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. + +*Which technologies are used?* + +- [Python](https://www.python.org) +- [Flask](http://flask.pocoo.org) +- [Peewee ORM](http://docs.peewee-orm.com) +- [Markdown](http://daringfireball.net/projects/markdown) + From 8fb6c5e23b185a15e4ea2ee9f387e92c7b6dc0a1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 25 Oct 2015 14:52:27 +0100 Subject: [PATCH 121/586] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b24eb9c..db998c1 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,7 @@ Stacosys can be hosted on the same server or on a different server than the blog ### FAQ *So the blog needs a server-side language?* -- Right! Stacosys is written in Python and it uses Flask Web framework. You - keeps on serving static pages for the blog but you have to link two URL with - Pecosys. If you use NginX or Apache2 (with Proxy modules), it's not a big - deal. +- It depends on your hosting configuration. Stacosys can run on a different host than the blog and it can serve several blogs. You have to change JS code embedded in blog pages to point the right Stacosys API URL. *How do you block spammers?* - That's a huge topic. Current comment form is basic: no captcha support but a honey @@ -49,3 +46,6 @@ Stacosys can be hosted on the same server or on a different server than the blog - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) +### Ways of improvement + +Current version of Stacosys fits my needs and it serves comments on [my blog](http://blogduyax.madyanne.fr) for 6 months. However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and to make e-mail communication optional. I encourage you to fork the project and create such improvements if you need them. I'll be happy to see the project evolving and growing according to users needs. From 5848b8df8e55076de6247baf59d120da9aae10e5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 25 Oct 2015 14:52:27 +0100 Subject: [PATCH 122/586] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b24eb9c..db998c1 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,7 @@ Stacosys can be hosted on the same server or on a different server than the blog ### FAQ *So the blog needs a server-side language?* -- Right! Stacosys is written in Python and it uses Flask Web framework. You - keeps on serving static pages for the blog but you have to link two URL with - Pecosys. If you use NginX or Apache2 (with Proxy modules), it's not a big - deal. +- It depends on your hosting configuration. Stacosys can run on a different host than the blog and it can serve several blogs. You have to change JS code embedded in blog pages to point the right Stacosys API URL. *How do you block spammers?* - That's a huge topic. Current comment form is basic: no captcha support but a honey @@ -49,3 +46,6 @@ Stacosys can be hosted on the same server or on a different server than the blog - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) +### Ways of improvement + +Current version of Stacosys fits my needs and it serves comments on [my blog](http://blogduyax.madyanne.fr) for 6 months. However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and to make e-mail communication optional. I encourage you to fork the project and create such improvements if you need them. I'll be happy to see the project evolving and growing according to users needs. From a2904d7a6d2461b2ea1b8c850c6b4a0ea4fb9854 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 May 2016 13:05:01 +0200 Subject: [PATCH 123/586] Set Flask logging level --- app/run.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/run.py b/app/run.py index a80df80..36a76b4 100644 --- a/app/run.py +++ b/app/run.py @@ -63,6 +63,11 @@ logger.info("Start Stacosys application") # enable CORS cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +# tune logging level +if not config.DEBUG: + logging.getLogger('flask_cors').level = logging.WARNING + logging.getLogger('werkzeug').level = logging.WARNING + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, use_reloader=False) From 7049e9e905614208393116a989a5bd487583c5a6 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 9 May 2016 13:05:01 +0200 Subject: [PATCH 124/586] Set Flask logging level --- app/run.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/run.py b/app/run.py index a80df80..36a76b4 100644 --- a/app/run.py +++ b/app/run.py @@ -63,6 +63,11 @@ logger.info("Start Stacosys application") # enable CORS cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +# tune logging level +if not config.DEBUG: + logging.getLogger('flask_cors').level = logging.WARNING + logging.getLogger('werkzeug').level = logging.WARNING + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, use_reloader=False) From 46bdd109215d96718de8151a2ac93a69f1c58f5d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 May 2016 13:10:11 +0200 Subject: [PATCH 125/586] Improve debug logging level --- app/controllers/api.py | 3 +-- app/run.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 9928afd..1879b6b 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -34,7 +34,7 @@ def query_comments(): if comment.author_email: d['avatar'] = md5(comment.author_email.strip().lower()) d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") - logger.info(d) + logger.debug(d) comments.append(d) r = jsonify({'data': comments}) r.status_code = 200 @@ -155,4 +155,3 @@ def reject_comment(): abort(500) return "REJECTED" - diff --git a/app/run.py b/app/run.py index 36a76b4..3cffc60 100644 --- a/app/run.py +++ b/app/run.py @@ -66,6 +66,7 @@ cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('flask_cors').level = logging.WARNING + logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING app.run(host=config.HTTP_ADDRESS, From f2d95da0639ce1091561805885c557ea1fdce2d0 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 9 May 2016 13:10:11 +0200 Subject: [PATCH 126/586] Improve debug logging level --- app/controllers/api.py | 3 +-- app/run.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 9928afd..1879b6b 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -34,7 +34,7 @@ def query_comments(): if comment.author_email: d['avatar'] = md5(comment.author_email.strip().lower()) d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") - logger.info(d) + logger.debug(d) comments.append(d) r = jsonify({'data': comments}) r.status_code = 200 @@ -155,4 +155,3 @@ def reject_comment(): abort(500) return "REJECTED" - diff --git a/app/run.py b/app/run.py index 36a76b4..3cffc60 100644 --- a/app/run.py +++ b/app/run.py @@ -66,6 +66,7 @@ cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('flask_cors').level = logging.WARNING + logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING app.run(host=config.HTTP_ADDRESS, From 881c525ae9dff2c0a2eed2f8fc676a0ed420252f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 May 2016 13:12:14 +0200 Subject: [PATCH 127/586] Improve debug logging level --- app/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/run.py b/app/run.py index 3cffc60..af65026 100644 --- a/app/run.py +++ b/app/run.py @@ -65,7 +65,6 @@ cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: - logging.getLogger('flask_cors').level = logging.WARNING logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING From f90af4fbad25a1e1179818a26919d9a55dbeb175 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 9 May 2016 13:12:14 +0200 Subject: [PATCH 128/586] Improve debug logging level --- app/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/run.py b/app/run.py index 3cffc60..af65026 100644 --- a/app/run.py +++ b/app/run.py @@ -65,7 +65,6 @@ cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: - logging.getLogger('flask_cors').level = logging.WARNING logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING From 4ff344df75415a34225839ff2d6574a663ad7db2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 May 2016 13:23:47 +0200 Subject: [PATCH 129/586] Catch exception on email reply processing. Issue #1 --- app/services/processor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 3d522c4..1b822b3 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -119,13 +119,16 @@ def reply_comment_email(data): break m = re.search('\[(\d+)\:(\w+)\]', subject) + if not m: + logger.warn('ignore corrupted email. No token %s' % subject) + return comment_id = int(m.group(1)) token = m.group(2) # retrieve site and comment rows comment = Comment.select().where(Comment.id == comment_id).get() if comment.site.token != token: - logger.warn('ignore corrupted email') + logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return if not message: @@ -334,7 +337,7 @@ def report(token): mail(site.admin_email, subject, email_body) - # delete report table + # delete report table Report.delete().execute() @@ -345,11 +348,12 @@ def rss(token): items = [] for row in Comment.select().join(Site).where( - Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + Site.token == token, Comment.published).order_by( + -Comment.published).limit(10): item_link = "http://%s%s" % (site.url, row.url) items.append(PyRSS2Gen.RSSItem( title='%s - http://%s%s' % (row.author_name, site.url, row.url), - link= item_link, + link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid(item_link), pubDate=row.published From a590d7c72ca415cd9e2ed258fb2f5aff8f10188b Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 9 May 2016 13:23:47 +0200 Subject: [PATCH 130/586] Catch exception on email reply processing. Issue #1 --- app/services/processor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 3d522c4..1b822b3 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -119,13 +119,16 @@ def reply_comment_email(data): break m = re.search('\[(\d+)\:(\w+)\]', subject) + if not m: + logger.warn('ignore corrupted email. No token %s' % subject) + return comment_id = int(m.group(1)) token = m.group(2) # retrieve site and comment rows comment = Comment.select().where(Comment.id == comment_id).get() if comment.site.token != token: - logger.warn('ignore corrupted email') + logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return if not message: @@ -334,7 +337,7 @@ def report(token): mail(site.admin_email, subject, email_body) - # delete report table + # delete report table Report.delete().execute() @@ -345,11 +348,12 @@ def rss(token): items = [] for row in Comment.select().join(Site).where( - Site.token == token, Comment.published).order_by(-Comment.published).limit(10): + Site.token == token, Comment.published).order_by( + -Comment.published).limit(10): item_link = "http://%s%s" % (site.url, row.url) items.append(PyRSS2Gen.RSSItem( title='%s - http://%s%s' % (row.author_name, site.url, row.url), - link= item_link, + link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid(item_link), pubDate=row.published From 104a48142e05e5bf0c86f8f374db79b6e4a9d184 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 8 Nov 2016 13:51:26 +0100 Subject: [PATCH 131/586] do not overwrite RSS file on app launch --- app/services/processor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 1b822b3..14ee41b 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os import logging import re from datetime import datetime @@ -341,7 +342,11 @@ def report(token): Report.delete().execute() -def rss(token): +def rss(token, onstart=False): + + if onstart and os.path.isfile(config.RSS_FILE): + return + site = Site.select().where(Site.token == token).get() rss_title = get_template('rss_title_message').render(site=site.name) md = markdown.Markdown() @@ -404,7 +409,7 @@ def start(template_dir): # generate RSS for all sites for site in Site.select(): - rss(site.token) + rss(site.token, True) # start processor thread proc = Processor() From db9c5437dd6498f8af57a5e35314a1babb61318f Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 8 Nov 2016 13:51:26 +0100 Subject: [PATCH 132/586] do not overwrite RSS file on app launch --- app/services/processor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/services/processor.py b/app/services/processor.py index 1b822b3..14ee41b 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os import logging import re from datetime import datetime @@ -341,7 +342,11 @@ def report(token): Report.delete().execute() -def rss(token): +def rss(token, onstart=False): + + if onstart and os.path.isfile(config.RSS_FILE): + return + site = Site.select().where(Site.token == token).get() rss_title = get_template('rss_title_message').render(site=site.name) md = markdown.Markdown() @@ -404,7 +409,7 @@ def start(template_dir): # generate RSS for all sites for site in Site.select(): - rss(site.token) + rss(site.token, True) # start processor thread proc = Processor() From 7cac877cc1e235cf42fa16f17485d8f7c3e66e56 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 14 Nov 2016 14:34:52 +0100 Subject: [PATCH 133/586] Fix RSS guid --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 14ee41b..363c8d8 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -360,7 +360,7 @@ def rss(token, onstart=False): title='%s - http://%s%s' % (row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid(item_link), + guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), pubDate=row.published )) From 6a1dbeab90999cc1295ef087e94bcf48cfcae801 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 14 Nov 2016 14:34:52 +0100 Subject: [PATCH 134/586] Fix RSS guid --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index 14ee41b..363c8d8 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -360,7 +360,7 @@ def rss(token, onstart=False): title='%s - http://%s%s' % (row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid(item_link), + guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), pubDate=row.published )) From b5e6bce22bc8a151706f4b29ac3c4a9f641d72a1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 7 Jul 2017 19:42:10 +0200 Subject: [PATCH 135/586] Ready for uwsgi --- app/run.py | 10 +++++----- app/services/processor.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/run.py b/app/run.py index af65026..e225170 100644 --- a/app/run.py +++ b/app/run.py @@ -56,8 +56,6 @@ logger.debug('imported: %s ' % reader.__name__) template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) -app.wsgi_app = ProxyFix(app.wsgi_app) - logger.info("Start Stacosys application") # enable CORS @@ -68,6 +66,8 @@ if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.run(host=config.HTTP_ADDRESS, - port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) +if __name__ == '__main__': + app.wsgi_app = ProxyFix(app.wsgi_app) + app.run(host=config.HTTP_ADDRESS, + port=config.HTTP_PORT, + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/processor.py b/app/services/processor.py index 363c8d8..7bba82d 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -33,6 +33,7 @@ class Processor(Thread): def run(self): + logger.info('processor thread started') self.is_running = True while self.is_running: try: From 6d61e7c41ff7765bf3a23175ecee12a75d03b7d6 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 7 Jul 2017 19:42:10 +0200 Subject: [PATCH 136/586] Ready for uwsgi --- app/run.py | 10 +++++----- app/services/processor.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/run.py b/app/run.py index af65026..e225170 100644 --- a/app/run.py +++ b/app/run.py @@ -56,8 +56,6 @@ logger.debug('imported: %s ' % reader.__name__) template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) -app.wsgi_app = ProxyFix(app.wsgi_app) - logger.info("Start Stacosys application") # enable CORS @@ -68,6 +66,8 @@ if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.run(host=config.HTTP_ADDRESS, - port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) +if __name__ == '__main__': + app.wsgi_app = ProxyFix(app.wsgi_app) + app.run(host=config.HTTP_ADDRESS, + port=config.HTTP_PORT, + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/processor.py b/app/services/processor.py index 363c8d8..7bba82d 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -33,6 +33,7 @@ class Processor(Thread): def run(self): + logger.info('processor thread started') self.is_running = True while self.is_running: try: From a22ef9797046a2b9bf43b6595b1d392b4f872895 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jul 2017 16:39:17 +0200 Subject: [PATCH 137/586] wsgi fix --- app/run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index e225170..fc8bd1a 100644 --- a/app/run.py +++ b/app/run.py @@ -66,8 +66,10 @@ if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING +app.wsgi_app = ProxyFix(app.wsgi_app) + if __name__ == '__main__': - app.wsgi_app = ProxyFix(app.wsgi_app) + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, use_reloader=False) From 657d5400f829d26e5b7c164522c0e3a3eae0ff39 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 8 Jul 2017 16:39:17 +0200 Subject: [PATCH 138/586] wsgi fix --- app/run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index e225170..fc8bd1a 100644 --- a/app/run.py +++ b/app/run.py @@ -66,8 +66,10 @@ if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING +app.wsgi_app = ProxyFix(app.wsgi_app) + if __name__ == '__main__': - app.wsgi_app = ProxyFix(app.wsgi_app) + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, use_reloader=False) From 19655b24dfb6f0c89448404e7b962ea82ca0c139 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jul 2017 18:53:49 +0200 Subject: [PATCH 139/586] flask cache --- app/__init__.py | 2 ++ app/controllers/api.py | 2 ++ requirements.txt | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index d7562aa..289b569 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,5 @@ from flask import Flask +from flask.ext.cache import Cache app = Flask(__name__) +cache = Cache(app, config={'CACHE_TYPE': 'simple'}) diff --git a/app/controllers/api.py b/app/controllers/api.py index 1879b6b..0e6e216 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -5,6 +5,7 @@ import logging import config from flask import request, jsonify, abort from app import app +from app import cache from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 @@ -45,6 +46,7 @@ def query_comments(): return r +@cache.cached(timeout=300) @app.route("/comments/count", methods=['GET']) def get_comments_count(): try: diff --git a/requirements.txt b/requirements.txt index 4b7c647..02673a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ clize==2.4 Flask==0.10.1 +Flask-Cache==0.13.1 Flask-Cors==2.0.1 itsdangerous==0.24 Jinja2==2.7.3 @@ -11,4 +12,3 @@ PyRSS2Gen==1.1 requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 -wheel==0.24.0 From 17c302f9992be5e0effcd710798820669cb52df7 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 8 Jul 2017 18:53:49 +0200 Subject: [PATCH 140/586] flask cache --- app/__init__.py | 2 ++ app/controllers/api.py | 2 ++ requirements.txt | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index d7562aa..289b569 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,5 @@ from flask import Flask +from flask.ext.cache import Cache app = Flask(__name__) +cache = Cache(app, config={'CACHE_TYPE': 'simple'}) diff --git a/app/controllers/api.py b/app/controllers/api.py index 1879b6b..0e6e216 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -5,6 +5,7 @@ import logging import config from flask import request, jsonify, abort from app import app +from app import cache from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 @@ -45,6 +46,7 @@ def query_comments(): return r +@cache.cached(timeout=300) @app.route("/comments/count", methods=['GET']) def get_comments_count(): try: diff --git a/requirements.txt b/requirements.txt index 4b7c647..02673a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ clize==2.4 Flask==0.10.1 +Flask-Cache==0.13.1 Flask-Cors==2.0.1 itsdangerous==0.24 Jinja2==2.7.3 @@ -11,4 +12,3 @@ PyRSS2Gen==1.1 requests==2.7.0 six==1.9.0 Werkzeug==0.10.4 -wheel==0.24.0 From 47008dc23ab938742e755b76b04fe7bd0c2480fa Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jul 2017 20:21:39 +0200 Subject: [PATCH 141/586] replace flask with sanic --- app/__init__.py | 7 +++-- app/controllers/api.py | 56 ++++++++++++++++++--------------------- app/controllers/mail.py | 6 ++--- app/controllers/reader.py | 8 +++--- app/run.py | 8 ++---- requirements.txt | 11 ++++---- 6 files changed, 44 insertions(+), 52 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 289b569..14f7a33 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,4 @@ -from flask import Flask -from flask.ext.cache import Cache +from sanic import Sanic -app = Flask(__name__) -cache = Cache(app, config={'CACHE_TYPE': 'simple'}) +app = Sanic() +cache = {} diff --git a/app/controllers/api.py b/app/controllers/api.py index 0e6e216..ddbdf67 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -3,7 +3,7 @@ import logging import config -from flask import request, jsonify, abort +from sanic import response from app import app from app import cache from app.models.site import Site @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) @app.route("/comments", methods=['GET']) -def query_comments(): +def query_comments(request): comments = [] try: @@ -37,18 +37,16 @@ def query_comments(): d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) - r.status_code = 200 + r = response.json({'data': comments}) except: logger.warn('bad request') - r = jsonify({'data': []}) - r.status_code = 400 + r = response.json({'data': []}, status=400) return r -@cache.cached(timeout=300) +#@cache.cached(timeout=300) @app.route("/comments/count", methods=['GET']) -def get_comments_count(): +def get_comments_count(request): try: token = request.args.get('token', '') url = request.args.get('url', '') @@ -56,16 +54,14 @@ def get_comments_count(): (Comment.url == url) & (Comment.published.is_null(False)) & (Site.token == token)).count() - r = jsonify({'count': count}) - r.status_code = 200 + r = response.json({'count': count}) except: - r = jsonify({'count': 0}) - r.status_code = 200 + r = response.json({'count': 0}) return r @app.route("/comments", methods=['POST']) -def new_comment(): +def new_comment(request): try: data = request.get_json() @@ -76,25 +72,25 @@ def new_comment(): site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - abort(400) + return response.text('BAD_REQUEST', status=400) # honeypot for spammers captcha = data.get('captcha', '') if captcha: logger.warn('discard spam: data %s' % data) - abort(400) + return response.text('BAD_REQUEST', status=400) processor.enqueue({'request': 'new_comment', 'data': data}) except: logger.exception("new comment failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') @app.route("/report", methods=['GET']) -def report(): +def report(request): try: token = request.args.get('token', '') @@ -102,25 +98,25 @@ def report(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - abort(404) + return response.text('', status=404) processor.enqueue({'request': 'report', 'data': token}) except: logger.exception("report failure") - abort(500) + return response.text('ERROR', status=500) - return "OK" + return response.text('OK') @app.route("/accept", methods=['GET']) -def accept_comment(): +def accept_comment(request): try: id = request.args.get('comment', '') @@ -128,19 +124,19 @@ def accept_comment(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) processor.enqueue({'request': 'late_accept', 'data': id}) except: logger.exception("accept failure") - abort(500) + return response.text('', status=500) - return "PUBLISHED" + return response.text('PUBLISHED') @app.route("/reject", methods=['GET']) -def reject_comment(): +def reject_comment(request): try: id = request.args.get('comment', '') @@ -148,12 +144,12 @@ def reject_comment(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) processor.enqueue({'request': 'late_reject', 'data': id}) except: logger.exception("reject failure") - abort(500) + return response.text('ERROR', status=500) - return "REJECTED" + return response.text('REJECTED') diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 7ade3fc..43d4d9c 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, abort +from sanic import response from app import app from app.services import processor @@ -10,10 +10,10 @@ logger = logging.getLogger(__name__) @app.route("/inbox", methods=['POST']) -def new_mail(): +def new_mail(request): try: - data = request.get_json() + data = request.json logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) diff --git a/app/controllers/reader.py b/app/controllers/reader.py index 2673105..0631861 100644 --- a/app/controllers/reader.py +++ b/app/controllers/reader.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, abort +from sanic import response from app import app from app.services import processor @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) @app.route("/unsubscribe", methods=['GET']) -def unsubscribe(): +def unsubscribe(request): try: data = { @@ -24,6 +24,6 @@ def unsubscribe(): except: logger.exception("unsubscribe failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') diff --git a/app/run.py b/app/run.py index fc8bd1a..05c3f43 100644 --- a/app/run.py +++ b/app/run.py @@ -4,8 +4,6 @@ import os import sys import logging -from werkzeug.contrib.fixers import ProxyFix -from flask.ext.cors import CORS # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -59,17 +57,15 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +#cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.wsgi_app = ProxyFix(app.wsgi_app) - if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) + debug=config.DEBUG) diff --git a/requirements.txt b/requirements.txt index 02673a5..ab6a057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ +aiofiles==0.3.1 clize==2.4 -Flask==0.10.1 -Flask-Cache==0.13.1 -Flask-Cors==2.0.1 -itsdangerous==0.24 +httptools==0.0.9 Jinja2==2.7.3 Markdown==2.6.2 MarkupSafe==0.23 @@ -10,5 +8,8 @@ peewee==2.6.0 PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 +sanic==0.5.4 six==1.9.0 -Werkzeug==0.10.4 +ujson==1.35 +uvloop==0.8.0 +websockets==3.3 From f00db855411d047255ba565a6799fa3a9f0f5de1 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 8 Jul 2017 20:21:39 +0200 Subject: [PATCH 142/586] replace flask with sanic --- app/__init__.py | 7 +++-- app/controllers/api.py | 56 ++++++++++++++++++--------------------- app/controllers/mail.py | 6 ++--- app/controllers/reader.py | 8 +++--- app/run.py | 8 ++---- requirements.txt | 11 ++++---- 6 files changed, 44 insertions(+), 52 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 289b569..14f7a33 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,4 @@ -from flask import Flask -from flask.ext.cache import Cache +from sanic import Sanic -app = Flask(__name__) -cache = Cache(app, config={'CACHE_TYPE': 'simple'}) +app = Sanic() +cache = {} diff --git a/app/controllers/api.py b/app/controllers/api.py index 0e6e216..ddbdf67 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -3,7 +3,7 @@ import logging import config -from flask import request, jsonify, abort +from sanic import response from app import app from app import cache from app.models.site import Site @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) @app.route("/comments", methods=['GET']) -def query_comments(): +def query_comments(request): comments = [] try: @@ -37,18 +37,16 @@ def query_comments(): d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) - r.status_code = 200 + r = response.json({'data': comments}) except: logger.warn('bad request') - r = jsonify({'data': []}) - r.status_code = 400 + r = response.json({'data': []}, status=400) return r -@cache.cached(timeout=300) +#@cache.cached(timeout=300) @app.route("/comments/count", methods=['GET']) -def get_comments_count(): +def get_comments_count(request): try: token = request.args.get('token', '') url = request.args.get('url', '') @@ -56,16 +54,14 @@ def get_comments_count(): (Comment.url == url) & (Comment.published.is_null(False)) & (Site.token == token)).count() - r = jsonify({'count': count}) - r.status_code = 200 + r = response.json({'count': count}) except: - r = jsonify({'count': 0}) - r.status_code = 200 + r = response.json({'count': 0}) return r @app.route("/comments", methods=['POST']) -def new_comment(): +def new_comment(request): try: data = request.get_json() @@ -76,25 +72,25 @@ def new_comment(): site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - abort(400) + return response.text('BAD_REQUEST', status=400) # honeypot for spammers captcha = data.get('captcha', '') if captcha: logger.warn('discard spam: data %s' % data) - abort(400) + return response.text('BAD_REQUEST', status=400) processor.enqueue({'request': 'new_comment', 'data': data}) except: logger.exception("new comment failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') @app.route("/report", methods=['GET']) -def report(): +def report(request): try: token = request.args.get('token', '') @@ -102,25 +98,25 @@ def report(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - abort(404) + return response.text('', status=404) processor.enqueue({'request': 'report', 'data': token}) except: logger.exception("report failure") - abort(500) + return response.text('ERROR', status=500) - return "OK" + return response.text('OK') @app.route("/accept", methods=['GET']) -def accept_comment(): +def accept_comment(request): try: id = request.args.get('comment', '') @@ -128,19 +124,19 @@ def accept_comment(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) processor.enqueue({'request': 'late_accept', 'data': id}) except: logger.exception("accept failure") - abort(500) + return response.text('', status=500) - return "PUBLISHED" + return response.text('PUBLISHED') @app.route("/reject", methods=['GET']) -def reject_comment(): +def reject_comment(request): try: id = request.args.get('comment', '') @@ -148,12 +144,12 @@ def reject_comment(): if secret != config.SECRET: logger.warn('Unauthorized request') - abort(401) + return response.text('UNAUTHORIZED', status=401) processor.enqueue({'request': 'late_reject', 'data': id}) except: logger.exception("reject failure") - abort(500) + return response.text('ERROR', status=500) - return "REJECTED" + return response.text('REJECTED') diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 7ade3fc..43d4d9c 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, abort +from sanic import response from app import app from app.services import processor @@ -10,10 +10,10 @@ logger = logging.getLogger(__name__) @app.route("/inbox", methods=['POST']) -def new_mail(): +def new_mail(request): try: - data = request.get_json() + data = request.json logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) diff --git a/app/controllers/reader.py b/app/controllers/reader.py index 2673105..0631861 100644 --- a/app/controllers/reader.py +++ b/app/controllers/reader.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from flask import request, abort +from sanic import response from app import app from app.services import processor @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) @app.route("/unsubscribe", methods=['GET']) -def unsubscribe(): +def unsubscribe(request): try: data = { @@ -24,6 +24,6 @@ def unsubscribe(): except: logger.exception("unsubscribe failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') diff --git a/app/run.py b/app/run.py index fc8bd1a..05c3f43 100644 --- a/app/run.py +++ b/app/run.py @@ -4,8 +4,6 @@ import os import sys import logging -from werkzeug.contrib.fixers import ProxyFix -from flask.ext.cors import CORS # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -59,17 +57,15 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +#cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.wsgi_app = ProxyFix(app.wsgi_app) - if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) + debug=config.DEBUG) diff --git a/requirements.txt b/requirements.txt index 02673a5..ab6a057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ +aiofiles==0.3.1 clize==2.4 -Flask==0.10.1 -Flask-Cache==0.13.1 -Flask-Cors==2.0.1 -itsdangerous==0.24 +httptools==0.0.9 Jinja2==2.7.3 Markdown==2.6.2 MarkupSafe==0.23 @@ -10,5 +8,8 @@ peewee==2.6.0 PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 +sanic==0.5.4 six==1.9.0 -Werkzeug==0.10.4 +ujson==1.35 +uvloop==0.8.0 +websockets==3.3 From 09bf8fd64124b086a08a79f157c490aa13742aa0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jul 2017 20:31:40 +0200 Subject: [PATCH 143/586] cors --- app/run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index 05c3f43..164b913 100644 --- a/app/run.py +++ b/app/run.py @@ -15,6 +15,8 @@ for path in paths: # more imports import config +from sanic_cors import CORS, cross_origin + from app.services import database from app.services import processor from app.controllers import api @@ -57,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -#cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: From 0f55ec361f3ac016d509bafdf89cfed150c54245 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 8 Jul 2017 20:31:40 +0200 Subject: [PATCH 144/586] cors --- app/run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index 05c3f43..164b913 100644 --- a/app/run.py +++ b/app/run.py @@ -15,6 +15,8 @@ for path in paths: # more imports import config +from sanic_cors import CORS, cross_origin + from app.services import database from app.services import processor from app.controllers import api @@ -57,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -#cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: From 2cbd4e96b8d1cbb74297140c8c32e6cdf2ff0dd4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jul 2017 20:42:58 +0200 Subject: [PATCH 145/586] configure workers --- app/run.py | 3 ++- config.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/run.py b/app/run.py index 164b913..57ccb40 100644 --- a/app/run.py +++ b/app/run.py @@ -70,4 +70,5 @@ if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG) + debug=config.DEBUG, + workers=config.HTTP_WORKERS) diff --git a/config.py b/config.py index 5dd761a..03e769f 100644 --- a/config.py +++ b/config.py @@ -11,6 +11,7 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 +HTTP_WORKERS = 4 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" @@ -18,4 +19,4 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' -RSS_FILE = 'comments.xml' \ No newline at end of file +RSS_FILE = 'comments.xml' From 7bbe99852d6af33b128e62064fac59fc16c12a12 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 8 Jul 2017 20:42:58 +0200 Subject: [PATCH 146/586] configure workers --- app/run.py | 3 ++- config.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/run.py b/app/run.py index 164b913..57ccb40 100644 --- a/app/run.py +++ b/app/run.py @@ -70,4 +70,5 @@ if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG) + debug=config.DEBUG, + workers=config.HTTP_WORKERS) diff --git a/config.py b/config.py index 5dd761a..03e769f 100644 --- a/config.py +++ b/config.py @@ -11,6 +11,7 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 +HTTP_WORKERS = 4 SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" @@ -18,4 +19,4 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' -RSS_FILE = 'comments.xml' \ No newline at end of file +RSS_FILE = 'comments.xml' From feff70415f77a650138446c95ded23bea922fea9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 11:27:16 +0200 Subject: [PATCH 147/586] sanic compliance --- app/controllers/api.py | 2 +- app/controllers/mail.py | 4 ++-- app/run.py | 2 +- config.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index ddbdf67..e6f364d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -64,7 +64,7 @@ def get_comments_count(request): def new_comment(request): try: - data = request.get_json() + data = request.json logger.info(data) # validate token: retrieve site entity diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 43d4d9c..0c7ba87 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -20,6 +20,6 @@ def new_mail(request): except: logger.exception("new mail failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') diff --git a/app/run.py b/app/run.py index 57ccb40..02262c0 100644 --- a/app/run.py +++ b/app/run.py @@ -59,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +cors = CORS(app, resources={r"/comments/*": {"origins": config.CORS_ORIGIN}}) # tune logging level if not config.DEBUG: diff --git a/config.py b/config.py index 03e769f..4d969dc 100644 --- a/config.py +++ b/config.py @@ -11,7 +11,8 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 -HTTP_WORKERS = 4 +HTTP_WORKERS = 1 +CORS_ORIGIN = "*" SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" From ae195d982670da07265f360ec5c2d839328e4d28 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 11:27:16 +0200 Subject: [PATCH 148/586] sanic compliance --- app/controllers/api.py | 2 +- app/controllers/mail.py | 4 ++-- app/run.py | 2 +- config.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index ddbdf67..e6f364d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -64,7 +64,7 @@ def get_comments_count(request): def new_comment(request): try: - data = request.get_json() + data = request.json logger.info(data) # validate token: retrieve site entity diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 43d4d9c..0c7ba87 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -20,6 +20,6 @@ def new_mail(request): except: logger.exception("new mail failure") - abort(400) + return response.text('BAD_REQUEST', status=400) - return "OK" + return response.text('OK') diff --git a/app/run.py b/app/run.py index 57ccb40..02262c0 100644 --- a/app/run.py +++ b/app/run.py @@ -59,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +cors = CORS(app, resources={r"/comments/*": {"origins": config.CORS_ORIGIN}}) # tune logging level if not config.DEBUG: diff --git a/config.py b/config.py index 03e769f..4d969dc 100644 --- a/config.py +++ b/config.py @@ -11,7 +11,8 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" HTTP_PORT = 8000 -HTTP_WORKERS = 4 +HTTP_WORKERS = 1 +CORS_ORIGIN = "*" SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" From 4fce3d7363e7d8a20599274e78ba7c77d65f27ed Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 11:39:12 +0200 Subject: [PATCH 149/586] Fix post --- app/controllers/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index e6f364d..23030e1 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -60,7 +60,7 @@ def get_comments_count(request): return r -@app.route("/comments", methods=['POST']) +@app.route("/comments", methods=['POST','OPTIONS']) def new_comment(request): try: From c0666262bef58c5452172d2162c9e930ffb82990 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 11:39:12 +0200 Subject: [PATCH 150/586] Fix post --- app/controllers/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index e6f364d..23030e1 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -60,7 +60,7 @@ def get_comments_count(request): return r -@app.route("/comments", methods=['POST']) +@app.route("/comments", methods=['POST','OPTIONS']) def new_comment(request): try: From 125acbad1c3dc31e9017f96152b4234845c648d4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 11:42:36 +0200 Subject: [PATCH 151/586] Debug post options --- app/controllers/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 23030e1..689e509 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -60,7 +60,12 @@ def get_comments_count(request): return r -@app.route("/comments", methods=['POST','OPTIONS']) +@app.route("/comments", methods=['OPTIONS']) +def option_comments(request): + return response.text('OK') + + +@app.route("/comments", methods=['POST']) def new_comment(request): try: From 537940381beba153a94b083e7c63c06348abb251 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 11:42:36 +0200 Subject: [PATCH 152/586] Debug post options --- app/controllers/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 23030e1..689e509 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -60,7 +60,12 @@ def get_comments_count(request): return r -@app.route("/comments", methods=['POST','OPTIONS']) +@app.route("/comments", methods=['OPTIONS']) +def option_comments(request): + return response.text('OK') + + +@app.route("/comments", methods=['POST']) def new_comment(request): try: From 85ad7ec89aa0ed71acf7fcbe0175a721292f3b22 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 14:25:19 +0200 Subject: [PATCH 153/586] cors change --- app/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index 02262c0..236a62b 100644 --- a/app/run.py +++ b/app/run.py @@ -59,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": config.CORS_ORIGIN}}) +cors = CORS(app, resources=r'/comments/*') # tune logging level if not config.DEBUG: From 15ebe08bd5477bb0f018b53e8bc227afe320aaa3 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 14:25:19 +0200 Subject: [PATCH 154/586] cors change --- app/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index 02262c0..236a62b 100644 --- a/app/run.py +++ b/app/run.py @@ -59,7 +59,7 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": config.CORS_ORIGIN}}) +cors = CORS(app, resources=r'/comments/*') # tune logging level if not config.DEBUG: From 48d45bfb2fe580d81151819bfd52b0108280b8b8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 15:27:59 +0200 Subject: [PATCH 155/586] cache count --- app/__init__.py | 3 ++- app/controllers/api.py | 3 ++- requirements.txt | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 14f7a33..a9c5ad1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,5 @@ from sanic import Sanic +from aiocache import SimpleMemoryCache app = Sanic() -cache = {} +cache = SimpleMemoryCache() diff --git a/app/controllers/api.py b/app/controllers/api.py index 689e509..8faa53f 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,6 +4,7 @@ import logging import config from sanic import response +from aiocache import cached from app import app from app import cache from app.models.site import Site @@ -44,7 +45,7 @@ def query_comments(request): return r -#@cache.cached(timeout=300) +@cached(ttl=300) @app.route("/comments/count", methods=['GET']) def get_comments_count(request): try: diff --git a/requirements.txt b/requirements.txt index ab6a057..cf37f42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ +aiocache==0.7.0 aiofiles==0.3.1 +aiomcache==0.5.2 +aioredis==0.3.3 clize==2.4 +hiredis==0.2.0 httptools==0.0.9 Jinja2==2.7.3 Markdown==2.6.2 @@ -9,6 +13,7 @@ PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 sanic==0.5.4 +Sanic-Cors==0.5.4.2 six==1.9.0 ujson==1.35 uvloop==0.8.0 From 19334523382e4731011d7c17da6916cba632c0a4 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 15:27:59 +0200 Subject: [PATCH 156/586] cache count --- app/__init__.py | 3 ++- app/controllers/api.py | 3 ++- requirements.txt | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 14f7a33..a9c5ad1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,5 @@ from sanic import Sanic +from aiocache import SimpleMemoryCache app = Sanic() -cache = {} +cache = SimpleMemoryCache() diff --git a/app/controllers/api.py b/app/controllers/api.py index 689e509..8faa53f 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,6 +4,7 @@ import logging import config from sanic import response +from aiocache import cached from app import app from app import cache from app.models.site import Site @@ -44,7 +45,7 @@ def query_comments(request): return r -#@cache.cached(timeout=300) +@cached(ttl=300) @app.route("/comments/count", methods=['GET']) def get_comments_count(request): try: diff --git a/requirements.txt b/requirements.txt index ab6a057..cf37f42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ +aiocache==0.7.0 aiofiles==0.3.1 +aiomcache==0.5.2 +aioredis==0.3.3 clize==2.4 +hiredis==0.2.0 httptools==0.0.9 Jinja2==2.7.3 Markdown==2.6.2 @@ -9,6 +13,7 @@ PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 sanic==0.5.4 +Sanic-Cors==0.5.4.2 six==1.9.0 ujson==1.35 uvloop==0.8.0 From f35fd42c0711b00d4a8fff0dcc1634afb4f5019b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 15:55:40 +0200 Subject: [PATCH 157/586] Fix count caching --- app/__init__.py | 2 -- app/controllers/api.py | 19 ++++++++++++------- app/run.py | 1 - 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a9c5ad1..20164f2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,3 @@ from sanic import Sanic -from aiocache import SimpleMemoryCache app = Sanic() -cache = SimpleMemoryCache() diff --git a/app/controllers/api.py b/app/controllers/api.py index 8faa53f..8494327 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,9 +4,9 @@ import logging import config from sanic import response -from aiocache import cached +from aiocache import cached, SimpleMemoryCache +from aiocache.serializers import JsonSerializer from app import app -from app import cache from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 @@ -45,22 +45,27 @@ def query_comments(request): return r -@cached(ttl=300) -@app.route("/comments/count", methods=['GET']) -def get_comments_count(request): +@cached(ttl=300, serializer=JsonSerializer()) +async def get_cached_comments_count(request): try: + print('GET COUNT FROM DB') token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( (Comment.url == url) & (Comment.published.is_null(False)) & (Site.token == token)).count() - r = response.json({'count': count}) + r = {'count': count} except: - r = response.json({'count': 0}) + r = {'count': 0} return r +@app.route("/comments/count", methods=['GET']) +async def get_comments_count(request): + return response.json(await get_cached_comments_count(request)) + + @app.route("/comments", methods=['OPTIONS']) def option_comments(request): return response.text('OK') diff --git a/app/run.py b/app/run.py index 236a62b..9402796 100644 --- a/app/run.py +++ b/app/run.py @@ -67,7 +67,6 @@ if not config.DEBUG: logging.getLogger('werkzeug').level = logging.WARNING if __name__ == '__main__': - app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, From f7bd755f33e8beaae7eb9791e2619efa29c4a0f2 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 15:55:40 +0200 Subject: [PATCH 158/586] Fix count caching --- app/__init__.py | 2 -- app/controllers/api.py | 19 ++++++++++++------- app/run.py | 1 - 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a9c5ad1..20164f2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,3 @@ from sanic import Sanic -from aiocache import SimpleMemoryCache app = Sanic() -cache = SimpleMemoryCache() diff --git a/app/controllers/api.py b/app/controllers/api.py index 8faa53f..8494327 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,9 +4,9 @@ import logging import config from sanic import response -from aiocache import cached +from aiocache import cached, SimpleMemoryCache +from aiocache.serializers import JsonSerializer from app import app -from app import cache from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 @@ -45,22 +45,27 @@ def query_comments(request): return r -@cached(ttl=300) -@app.route("/comments/count", methods=['GET']) -def get_comments_count(request): +@cached(ttl=300, serializer=JsonSerializer()) +async def get_cached_comments_count(request): try: + print('GET COUNT FROM DB') token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( (Comment.url == url) & (Comment.published.is_null(False)) & (Site.token == token)).count() - r = response.json({'count': count}) + r = {'count': count} except: - r = response.json({'count': 0}) + r = {'count': 0} return r +@app.route("/comments/count", methods=['GET']) +async def get_comments_count(request): + return response.json(await get_cached_comments_count(request)) + + @app.route("/comments", methods=['OPTIONS']) def option_comments(request): return response.text('OK') diff --git a/app/run.py b/app/run.py index 236a62b..9402796 100644 --- a/app/run.py +++ b/app/run.py @@ -67,7 +67,6 @@ if not config.DEBUG: logging.getLogger('werkzeug').level = logging.WARNING if __name__ == '__main__': - app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, From c9b1444bb62db1bc14b3155dd86d26878c726e17 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 15:56:17 +0200 Subject: [PATCH 159/586] Fix count caching --- app/controllers/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 8494327..d7f4148 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -48,7 +48,6 @@ def query_comments(request): @cached(ttl=300, serializer=JsonSerializer()) async def get_cached_comments_count(request): try: - print('GET COUNT FROM DB') token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( From 39e547c3030d6a6b9b0fed8d27fefcc344096dbc Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 15:56:17 +0200 Subject: [PATCH 160/586] Fix count caching --- app/controllers/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 8494327..d7f4148 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -48,7 +48,6 @@ def query_comments(request): @cached(ttl=300, serializer=JsonSerializer()) async def get_cached_comments_count(request): try: - print('GET COUNT FROM DB') token = request.args.get('token', '') url = request.args.get('url', '') count = Comment.select(Comment).join(Site).where( From 198978a35d0e73abc3e20e1c25e50cd2aed47bdd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 16:53:58 +0200 Subject: [PATCH 161/586] homemade caching function --- app/__init__.py | 16 ++++++++++++++++ app/controllers/api.py | 18 +++++++++++------- app/run.py | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 20164f2..86d275c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,19 @@ +import time from sanic import Sanic app = Sanic() +cache = {} +cache_time = 0 + +def get_cached(key): + global cache + global cache_time + value = cache.get(key,None) + if (time.time() - cache_time) > 120: + cache = {} + cache_time = time.time() + return value + +def set_cached(key, value): + global cache + cache[key] = value diff --git a/app/controllers/api.py b/app/controllers/api.py index d7f4148..58bdea5 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,13 +4,13 @@ import logging import config from sanic import response -from aiocache import cached, SimpleMemoryCache -from aiocache.serializers import JsonSerializer from app import app from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 from app.services import processor +from app import get_cached +from app import set_cached logger = logging.getLogger(__name__) @@ -45,17 +45,21 @@ def query_comments(request): return r -@cached(ttl=300, serializer=JsonSerializer()) async def get_cached_comments_count(request): try: token = request.args.get('token', '') url = request.args.get('url', '') - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() + key = '%s:%s' % (token, url) + count = get_cached(key) + if count is None: + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() + set_cached(key, count) r = {'count': count} except: + logger.exception("cache exception") r = {'count': 0} return r diff --git a/app/run.py b/app/run.py index 9402796..5c821a0 100644 --- a/app/run.py +++ b/app/run.py @@ -70,4 +70,5 @@ if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, + log_config=None, workers=config.HTTP_WORKERS) From c6cf9ac795617b21f9a8c3dfd0f5d22165cf72f5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 16:53:58 +0200 Subject: [PATCH 162/586] homemade caching function --- app/__init__.py | 16 ++++++++++++++++ app/controllers/api.py | 18 +++++++++++------- app/run.py | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 20164f2..86d275c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,19 @@ +import time from sanic import Sanic app = Sanic() +cache = {} +cache_time = 0 + +def get_cached(key): + global cache + global cache_time + value = cache.get(key,None) + if (time.time() - cache_time) > 120: + cache = {} + cache_time = time.time() + return value + +def set_cached(key, value): + global cache + cache[key] = value diff --git a/app/controllers/api.py b/app/controllers/api.py index d7f4148..58bdea5 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -4,13 +4,13 @@ import logging import config from sanic import response -from aiocache import cached, SimpleMemoryCache -from aiocache.serializers import JsonSerializer from app import app from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 from app.services import processor +from app import get_cached +from app import set_cached logger = logging.getLogger(__name__) @@ -45,17 +45,21 @@ def query_comments(request): return r -@cached(ttl=300, serializer=JsonSerializer()) async def get_cached_comments_count(request): try: token = request.args.get('token', '') url = request.args.get('url', '') - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() + key = '%s:%s' % (token, url) + count = get_cached(key) + if count is None: + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() + set_cached(key, count) r = {'count': count} except: + logger.exception("cache exception") r = {'count': 0} return r diff --git a/app/run.py b/app/run.py index 9402796..5c821a0 100644 --- a/app/run.py +++ b/app/run.py @@ -70,4 +70,5 @@ if __name__ == '__main__': app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, debug=config.DEBUG, + log_config=None, workers=config.HTTP_WORKERS) From c0e5255bee4e3232b87ba060d1c195e9752f3b05 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 17:13:41 +0200 Subject: [PATCH 163/586] one cache per worker --- app/__init__.py | 16 ---------------- app/controllers/api.py | 18 ++++++++++++++++-- requirements.txt | 3 --- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 86d275c..20164f2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,19 +1,3 @@ -import time from sanic import Sanic app = Sanic() -cache = {} -cache_time = 0 - -def get_cached(key): - global cache - global cache_time - value = cache.get(key,None) - if (time.time() - cache_time) > 120: - cache = {} - cache_time = time.time() - return value - -def set_cached(key, value): - global cache - cache[key] = value diff --git a/app/controllers/api.py b/app/controllers/api.py index 58bdea5..5040865 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import time import logging import config from sanic import response @@ -9,10 +10,23 @@ from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 from app.services import processor -from app import get_cached -from app import set_cached logger = logging.getLogger(__name__) +cache = {} +cache_time = 0 + +def get_cached(key): + global cache + global cache_time + value = cache.get(key,None) + if (time.time() - cache_time) > 10: + cache = {} + cache_time = time.time() + return value + +def set_cached(key, value): + global cache + cache[key] = value @app.route("/comments", methods=['GET']) diff --git a/requirements.txt b/requirements.txt index cf37f42..a06b7da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ -aiocache==0.7.0 aiofiles==0.3.1 -aiomcache==0.5.2 -aioredis==0.3.3 clize==2.4 hiredis==0.2.0 httptools==0.0.9 From c5ae33f73888ad9552e5e4ab51222b56ee77f640 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 17:13:41 +0200 Subject: [PATCH 164/586] one cache per worker --- app/__init__.py | 16 ---------------- app/controllers/api.py | 18 ++++++++++++++++-- requirements.txt | 3 --- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 86d275c..20164f2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,19 +1,3 @@ -import time from sanic import Sanic app = Sanic() -cache = {} -cache_time = 0 - -def get_cached(key): - global cache - global cache_time - value = cache.get(key,None) - if (time.time() - cache_time) > 120: - cache = {} - cache_time = time.time() - return value - -def set_cached(key, value): - global cache - cache[key] = value diff --git a/app/controllers/api.py b/app/controllers/api.py index 58bdea5..5040865 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import time import logging import config from sanic import response @@ -9,10 +10,23 @@ from app.models.site import Site from app.models.comment import Comment from app.helpers.hashing import md5 from app.services import processor -from app import get_cached -from app import set_cached logger = logging.getLogger(__name__) +cache = {} +cache_time = 0 + +def get_cached(key): + global cache + global cache_time + value = cache.get(key,None) + if (time.time() - cache_time) > 10: + cache = {} + cache_time = time.time() + return value + +def set_cached(key, value): + global cache + cache[key] = value @app.route("/comments", methods=['GET']) diff --git a/requirements.txt b/requirements.txt index cf37f42..a06b7da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ -aiocache==0.7.0 aiofiles==0.3.1 -aiomcache==0.5.2 -aioredis==0.3.3 clize==2.4 hiredis==0.2.0 httptools==0.0.9 From e2ea6b897002f2e6bff1b4bb351f299b76ab99fd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jul 2017 17:18:30 +0200 Subject: [PATCH 165/586] Cache time to 120s --- app/controllers/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5040865..5e66390 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -19,7 +19,7 @@ def get_cached(key): global cache global cache_time value = cache.get(key,None) - if (time.time() - cache_time) > 10: + if (time.time() - cache_time) > 120: cache = {} cache_time = time.time() return value From d740801eedd15117fbfd1718bd13009775ad7066 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 9 Jul 2017 17:18:30 +0200 Subject: [PATCH 166/586] Cache time to 120s --- app/controllers/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5040865..5e66390 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -19,7 +19,7 @@ def get_cached(key): global cache global cache_time value = cache.get(key,None) - if (time.time() - cache_time) > 10: + if (time.time() - cache_time) > 120: cache = {} cache_time = time.time() return value From cff260b92c912c43467d34e1fb7508cabf13e026 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Jul 2017 13:26:36 +0200 Subject: [PATCH 167/586] Draft Go HTTP server --- go-http/httpserver.go | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 go-http/httpserver.go diff --git a/go-http/httpserver.go b/go-http/httpserver.go new file mode 100644 index 0000000..75bc3bb --- /dev/null +++ b/go-http/httpserver.go @@ -0,0 +1,63 @@ +package main + +import ( + "github.com/patrickmn/go-cache" + "io" + "io/ioutil" + "log" + "net/http" + "time" +) + +const stacoURL string = "http://127.0.0.1:8100" + +var countCache = cache.New(5*time.Minute, 10*time.Minute) + +func commentsCount(w http.ResponseWriter, r *http.Request) { + + // only GET method is supported + if r.Method != "GET" { + http.NotFound(w, r) + return + } + + // set header + w.Header().Add("Content-Type", "application/json") + w.Header().Add("Access-Control-Allow-Origin", "*") + + // get cached value + cachedBody, found := countCache.Get(r.URL.String()) + if found { + //log.Printf("return cached value") + w.Write([]byte(cachedBody.(string))) + return + } + + // relay request to stacosys + response, err := http.Get(stacoURL + r.URL.String()) + if err != nil { + http.NotFound(w, r) + return + } + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + http.NotFound(w, r) + return + } + + // cache body and return response + countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) + log.Printf(string(body)) + w.Write(body) +} + +func comments(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "Hello world!") +} + +func main() { + http.HandleFunc("/comments/count", commentsCount) + http.HandleFunc("/comments", comments) + http.ListenAndServe(":8200", nil) +} From d723c2bbd34574a2d608df88acedb5464df2a6b0 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 16 Jul 2017 13:26:36 +0200 Subject: [PATCH 168/586] Draft Go HTTP server --- go-http/httpserver.go | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 go-http/httpserver.go diff --git a/go-http/httpserver.go b/go-http/httpserver.go new file mode 100644 index 0000000..75bc3bb --- /dev/null +++ b/go-http/httpserver.go @@ -0,0 +1,63 @@ +package main + +import ( + "github.com/patrickmn/go-cache" + "io" + "io/ioutil" + "log" + "net/http" + "time" +) + +const stacoURL string = "http://127.0.0.1:8100" + +var countCache = cache.New(5*time.Minute, 10*time.Minute) + +func commentsCount(w http.ResponseWriter, r *http.Request) { + + // only GET method is supported + if r.Method != "GET" { + http.NotFound(w, r) + return + } + + // set header + w.Header().Add("Content-Type", "application/json") + w.Header().Add("Access-Control-Allow-Origin", "*") + + // get cached value + cachedBody, found := countCache.Get(r.URL.String()) + if found { + //log.Printf("return cached value") + w.Write([]byte(cachedBody.(string))) + return + } + + // relay request to stacosys + response, err := http.Get(stacoURL + r.URL.String()) + if err != nil { + http.NotFound(w, r) + return + } + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + http.NotFound(w, r) + return + } + + // cache body and return response + countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) + log.Printf(string(body)) + w.Write(body) +} + +func comments(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "Hello world!") +} + +func main() { + http.HandleFunc("/comments/count", commentsCount) + http.HandleFunc("/comments", comments) + http.ListenAndServe(":8200", nil) +} From 4ca60c9f7db75b6a714bedd725a44e1d61094a76 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Jul 2017 16:32:16 +0200 Subject: [PATCH 169/586] Finalize http go --- go-http/config.json | 5 +++ go-http/{httpserver.go => httpfastcache.go} | 44 ++++++++++++++++----- 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 go-http/config.json rename go-http/{httpserver.go => httpfastcache.go} (55%) diff --git a/go-http/config.json b/go-http/config.json new file mode 100644 index 0000000..61e7b52 --- /dev/null +++ b/go-http/config.json @@ -0,0 +1,5 @@ +{ + "HostPort":"127.0.0.1:8101", + "Stacosys":"http://127.0.0.1:8100", + "CorsOrigin":"blogduyax.madyanne.fr" +} diff --git a/go-http/httpserver.go b/go-http/httpfastcache.go similarity index 55% rename from go-http/httpserver.go rename to go-http/httpfastcache.go index 75bc3bb..504bed9 100644 --- a/go-http/httpserver.go +++ b/go-http/httpfastcache.go @@ -1,18 +1,32 @@ package main import ( + "encoding/json" + "flag" + "fmt" "github.com/patrickmn/go-cache" - "io" "io/ioutil" "log" "net/http" + "os" "time" ) -const stacoURL string = "http://127.0.0.1:8100" +// ConfigType represents config info +type ConfigType struct { + HostPort string + Stacosys string + CorsOrigin string +} +var config ConfigType var countCache = cache.New(5*time.Minute, 10*time.Minute) +func die(format string, v ...interface{}) { + fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...)) + os.Exit(1) +} + func commentsCount(w http.ResponseWriter, r *http.Request) { // only GET method is supported @@ -23,7 +37,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // set header w.Header().Add("Content-Type", "application/json") - w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Origin", config.CorsOrigin) // get cached value cachedBody, found := countCache.Get(r.URL.String()) @@ -34,7 +48,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - response, err := http.Get(stacoURL + r.URL.String()) + response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) return @@ -52,12 +66,22 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func comments(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "Hello world!") -} - func main() { + pathname := flag.String("config", "", "config pathname") + flag.Parse() + if *pathname == "" { + die("%s --config ", os.Args[0]) + } + // read config File + file, e := ioutil.ReadFile(*pathname) + if e != nil { + die("File error: %v", e) + } + fmt.Printf("config: %s\n", string(file)) + + config := ConfigType{} + json.Unmarshal(file, &config) + http.HandleFunc("/comments/count", commentsCount) - http.HandleFunc("/comments", comments) - http.ListenAndServe(":8200", nil) + http.ListenAndServe(config.HostPort, nil) } From 7a8e0860738ec13d1002909c92342adfeca1520a Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 16 Jul 2017 16:32:16 +0200 Subject: [PATCH 170/586] Finalize http go --- go-http/config.json | 5 +++ go-http/{httpserver.go => httpfastcache.go} | 44 ++++++++++++++++----- 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 go-http/config.json rename go-http/{httpserver.go => httpfastcache.go} (55%) diff --git a/go-http/config.json b/go-http/config.json new file mode 100644 index 0000000..61e7b52 --- /dev/null +++ b/go-http/config.json @@ -0,0 +1,5 @@ +{ + "HostPort":"127.0.0.1:8101", + "Stacosys":"http://127.0.0.1:8100", + "CorsOrigin":"blogduyax.madyanne.fr" +} diff --git a/go-http/httpserver.go b/go-http/httpfastcache.go similarity index 55% rename from go-http/httpserver.go rename to go-http/httpfastcache.go index 75bc3bb..504bed9 100644 --- a/go-http/httpserver.go +++ b/go-http/httpfastcache.go @@ -1,18 +1,32 @@ package main import ( + "encoding/json" + "flag" + "fmt" "github.com/patrickmn/go-cache" - "io" "io/ioutil" "log" "net/http" + "os" "time" ) -const stacoURL string = "http://127.0.0.1:8100" +// ConfigType represents config info +type ConfigType struct { + HostPort string + Stacosys string + CorsOrigin string +} +var config ConfigType var countCache = cache.New(5*time.Minute, 10*time.Minute) +func die(format string, v ...interface{}) { + fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...)) + os.Exit(1) +} + func commentsCount(w http.ResponseWriter, r *http.Request) { // only GET method is supported @@ -23,7 +37,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // set header w.Header().Add("Content-Type", "application/json") - w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Origin", config.CorsOrigin) // get cached value cachedBody, found := countCache.Get(r.URL.String()) @@ -34,7 +48,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - response, err := http.Get(stacoURL + r.URL.String()) + response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) return @@ -52,12 +66,22 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func comments(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "Hello world!") -} - func main() { + pathname := flag.String("config", "", "config pathname") + flag.Parse() + if *pathname == "" { + die("%s --config ", os.Args[0]) + } + // read config File + file, e := ioutil.ReadFile(*pathname) + if e != nil { + die("File error: %v", e) + } + fmt.Printf("config: %s\n", string(file)) + + config := ConfigType{} + json.Unmarshal(file, &config) + http.HandleFunc("/comments/count", commentsCount) - http.HandleFunc("/comments", comments) - http.ListenAndServe(":8200", nil) + http.ListenAndServe(config.HostPort, nil) } From 4b54b97b161921403202ee863bcf88169851c532 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Jul 2017 17:19:56 +0200 Subject: [PATCH 171/586] Fix URL --- go-http/httpfastcache.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index 504bed9..16a1cc4 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/patrickmn/go-cache" "io/ioutil" - "log" "net/http" "os" "time" @@ -42,12 +41,13 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // get cached value cachedBody, found := countCache.Get(r.URL.String()) if found { - //log.Printf("return cached value") + //fmt.Printf("return cached value") w.Write([]byte(cachedBody.(string))) return } // relay request to stacosys + //fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -62,7 +62,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // cache body and return response countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - log.Printf(string(body)) + fmt.Printf(string(body)) w.Write(body) } @@ -77,10 +77,8 @@ func main() { if e != nil { die("File error: %v", e) } - fmt.Printf("config: %s\n", string(file)) - - config := ConfigType{} json.Unmarshal(file, &config) + fmt.Printf("config: %s\n", string(file)) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) From 7cbcb0b5cb2a59d13fcc0be82f027baff49af418 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 16 Jul 2017 17:19:56 +0200 Subject: [PATCH 172/586] Fix URL --- go-http/httpfastcache.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index 504bed9..16a1cc4 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/patrickmn/go-cache" "io/ioutil" - "log" "net/http" "os" "time" @@ -42,12 +41,13 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // get cached value cachedBody, found := countCache.Get(r.URL.String()) if found { - //log.Printf("return cached value") + //fmt.Printf("return cached value") w.Write([]byte(cachedBody.(string))) return } // relay request to stacosys + //fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -62,7 +62,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // cache body and return response countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - log.Printf(string(body)) + fmt.Printf(string(body)) w.Write(body) } @@ -77,10 +77,8 @@ func main() { if e != nil { die("File error: %v", e) } - fmt.Printf("config: %s\n", string(file)) - - config := ConfigType{} json.Unmarshal(file, &config) + fmt.Printf("config: %s\n", string(file)) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) From 64f54593ec735f4606e137e9168bf5f9cdf07fa3 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Jul 2017 17:35:38 +0200 Subject: [PATCH 173/586] back to flask :-) --- app/__init__.py | 4 +- app/controllers/api.py | 104 ++++++++++++++------------------------ app/controllers/mail.py | 10 ++-- app/controllers/reader.py | 8 +-- app/run.py | 13 ++--- app/services/__init__.py | 0 app/services/processor.py | 6 +-- config.py | 3 +- go-http/httpfastcache.go | 3 +- requirements.txt | 13 ++--- 10 files changed, 69 insertions(+), 95 deletions(-) create mode 100644 app/services/__init__.py diff --git a/app/__init__.py b/app/__init__.py index 20164f2..d7562aa 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,3 @@ -from sanic import Sanic +from flask import Flask -app = Sanic() +app = Flask(__name__) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5e66390..1879b6b 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -1,10 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import time import logging import config -from sanic import response +from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment @@ -12,25 +11,10 @@ from app.helpers.hashing import md5 from app.services import processor logger = logging.getLogger(__name__) -cache = {} -cache_time = 0 - -def get_cached(key): - global cache - global cache_time - value = cache.get(key,None) - if (time.time() - cache_time) > 120: - cache = {} - cache_time = time.time() - return value - -def set_cached(key, value): - global cache - cache[key] = value @app.route("/comments", methods=['GET']) -def query_comments(request): +def query_comments(): comments = [] try: @@ -52,47 +36,37 @@ def query_comments(request): d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = response.json({'data': comments}) + r = jsonify({'data': comments}) + r.status_code = 200 except: logger.warn('bad request') - r = response.json({'data': []}, status=400) - return r - - -async def get_cached_comments_count(request): - try: - token = request.args.get('token', '') - url = request.args.get('url', '') - key = '%s:%s' % (token, url) - count = get_cached(key) - if count is None: - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() - set_cached(key, count) - r = {'count': count} - except: - logger.exception("cache exception") - r = {'count': 0} + r = jsonify({'data': []}) + r.status_code = 400 return r @app.route("/comments/count", methods=['GET']) -async def get_comments_count(request): - return response.json(await get_cached_comments_count(request)) - - -@app.route("/comments", methods=['OPTIONS']) -def option_comments(request): - return response.text('OK') +def get_comments_count(): + try: + token = request.args.get('token', '') + url = request.args.get('url', '') + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() + r = jsonify({'count': count}) + r.status_code = 200 + except: + r = jsonify({'count': 0}) + r.status_code = 200 + return r @app.route("/comments", methods=['POST']) -def new_comment(request): +def new_comment(): try: - data = request.json + data = request.get_json() logger.info(data) # validate token: retrieve site entity @@ -100,25 +74,25 @@ def new_comment(request): site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - return response.text('BAD_REQUEST', status=400) + abort(400) # honeypot for spammers captcha = data.get('captcha', '') if captcha: logger.warn('discard spam: data %s' % data) - return response.text('BAD_REQUEST', status=400) + abort(400) processor.enqueue({'request': 'new_comment', 'data': data}) except: logger.exception("new comment failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" @app.route("/report", methods=['GET']) -def report(request): +def report(): try: token = request.args.get('token', '') @@ -126,25 +100,25 @@ def report(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - return response.text('', status=404) + abort(404) processor.enqueue({'request': 'report', 'data': token}) except: logger.exception("report failure") - return response.text('ERROR', status=500) + abort(500) - return response.text('OK') + return "OK" @app.route("/accept", methods=['GET']) -def accept_comment(request): +def accept_comment(): try: id = request.args.get('comment', '') @@ -152,19 +126,19 @@ def accept_comment(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) processor.enqueue({'request': 'late_accept', 'data': id}) except: logger.exception("accept failure") - return response.text('', status=500) + abort(500) - return response.text('PUBLISHED') + return "PUBLISHED" @app.route("/reject", methods=['GET']) -def reject_comment(request): +def reject_comment(): try: id = request.args.get('comment', '') @@ -172,12 +146,12 @@ def reject_comment(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) processor.enqueue({'request': 'late_reject', 'data': id}) except: logger.exception("reject failure") - return response.text('ERROR', status=500) + abort(500) - return response.text('REJECTED') + return "REJECTED" diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 0c7ba87..7ade3fc 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from sanic import response +from flask import request, abort from app import app from app.services import processor @@ -10,16 +10,16 @@ logger = logging.getLogger(__name__) @app.route("/inbox", methods=['POST']) -def new_mail(request): +def new_mail(): try: - data = request.json + data = request.get_json() logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) except: logger.exception("new mail failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" diff --git a/app/controllers/reader.py b/app/controllers/reader.py index 0631861..2673105 100644 --- a/app/controllers/reader.py +++ b/app/controllers/reader.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from sanic import response +from flask import request, abort from app import app from app.services import processor @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) @app.route("/unsubscribe", methods=['GET']) -def unsubscribe(request): +def unsubscribe(): try: data = { @@ -24,6 +24,6 @@ def unsubscribe(request): except: logger.exception("unsubscribe failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" diff --git a/app/run.py b/app/run.py index 5c821a0..fc8bd1a 100644 --- a/app/run.py +++ b/app/run.py @@ -4,6 +4,8 @@ import os import sys import logging +from werkzeug.contrib.fixers import ProxyFix +from flask.ext.cors import CORS # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -15,8 +17,6 @@ for path in paths: # more imports import config -from sanic_cors import CORS, cross_origin - from app.services import database from app.services import processor from app.controllers import api @@ -59,16 +59,17 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources=r'/comments/*') +cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING +app.wsgi_app = ProxyFix(app.wsgi_app) + if __name__ == '__main__': + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG, - log_config=None, - workers=config.HTTP_WORKERS) + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/processor.py b/app/services/processor.py index 7bba82d..2c18536 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -356,9 +356,9 @@ def rss(token, onstart=False): for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by( -Comment.published).limit(10): - item_link = "http://%s%s" % (site.url, row.url) + item_link = "%s://%s%s" % (config.RSS_URL_PROTO, site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - http://%s%s' % (row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.RSS_URL_PROTO, row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -367,7 +367,7 @@ def rss(token, onstart=False): rss = PyRSS2Gen.RSS2( title=rss_title, - link="http://" + site.url, + link='%s://%s' % (config.RSS_URL_PROTO, site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), items=items) diff --git a/config.py b/config.py index 4d969dc..16f780a 100644 --- a/config.py +++ b/config.py @@ -10,7 +10,7 @@ DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" -HTTP_PORT = 8000 +HTTP_PORT = 8100 HTTP_WORKERS = 1 CORS_ORIGIN = "*" @@ -20,4 +20,5 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' +RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index 16a1cc4..dd09c42 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -47,7 +47,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - //fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) + fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -80,6 +80,7 @@ func main() { json.Unmarshal(file, &config) fmt.Printf("config: %s\n", string(file)) + http.HandleFunc("/comments/count/", commentsCount) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) } diff --git a/requirements.txt b/requirements.txt index a06b7da..cc8994e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ -aiofiles==0.3.1 +click==6.7 clize==2.4 -hiredis==0.2.0 -httptools==0.0.9 +Flask==0.12.2 +Flask-Cors==3.0.3 +itsdangerous==0.24 Jinja2==2.7.3 Markdown==2.6.2 MarkupSafe==0.23 @@ -9,9 +10,5 @@ peewee==2.6.0 PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 -sanic==0.5.4 -Sanic-Cors==0.5.4.2 six==1.9.0 -ujson==1.35 -uvloop==0.8.0 -websockets==3.3 +Werkzeug==0.12.2 From 7deccf7425eeec7de984b4e179e0179abc637a29 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 16 Jul 2017 17:35:38 +0200 Subject: [PATCH 174/586] back to flask :-) --- app/__init__.py | 4 +- app/controllers/api.py | 104 ++++++++++++++------------------------ app/controllers/mail.py | 10 ++-- app/controllers/reader.py | 8 +-- app/run.py | 13 ++--- app/services/__init__.py | 0 app/services/processor.py | 6 +-- config.py | 3 +- go-http/httpfastcache.go | 3 +- requirements.txt | 13 ++--- 10 files changed, 69 insertions(+), 95 deletions(-) create mode 100644 app/services/__init__.py diff --git a/app/__init__.py b/app/__init__.py index 20164f2..d7562aa 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,3 @@ -from sanic import Sanic +from flask import Flask -app = Sanic() +app = Flask(__name__) diff --git a/app/controllers/api.py b/app/controllers/api.py index 5e66390..1879b6b 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -1,10 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import time import logging import config -from sanic import response +from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment @@ -12,25 +11,10 @@ from app.helpers.hashing import md5 from app.services import processor logger = logging.getLogger(__name__) -cache = {} -cache_time = 0 - -def get_cached(key): - global cache - global cache_time - value = cache.get(key,None) - if (time.time() - cache_time) > 120: - cache = {} - cache_time = time.time() - return value - -def set_cached(key, value): - global cache - cache[key] = value @app.route("/comments", methods=['GET']) -def query_comments(request): +def query_comments(): comments = [] try: @@ -52,47 +36,37 @@ def query_comments(request): d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = response.json({'data': comments}) + r = jsonify({'data': comments}) + r.status_code = 200 except: logger.warn('bad request') - r = response.json({'data': []}, status=400) - return r - - -async def get_cached_comments_count(request): - try: - token = request.args.get('token', '') - url = request.args.get('url', '') - key = '%s:%s' % (token, url) - count = get_cached(key) - if count is None: - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() - set_cached(key, count) - r = {'count': count} - except: - logger.exception("cache exception") - r = {'count': 0} + r = jsonify({'data': []}) + r.status_code = 400 return r @app.route("/comments/count", methods=['GET']) -async def get_comments_count(request): - return response.json(await get_cached_comments_count(request)) - - -@app.route("/comments", methods=['OPTIONS']) -def option_comments(request): - return response.text('OK') +def get_comments_count(): + try: + token = request.args.get('token', '') + url = request.args.get('url', '') + count = Comment.select(Comment).join(Site).where( + (Comment.url == url) & + (Comment.published.is_null(False)) & + (Site.token == token)).count() + r = jsonify({'count': count}) + r.status_code = 200 + except: + r = jsonify({'count': 0}) + r.status_code = 200 + return r @app.route("/comments", methods=['POST']) -def new_comment(request): +def new_comment(): try: - data = request.json + data = request.get_json() logger.info(data) # validate token: retrieve site entity @@ -100,25 +74,25 @@ def new_comment(request): site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - return response.text('BAD_REQUEST', status=400) + abort(400) # honeypot for spammers captcha = data.get('captcha', '') if captcha: logger.warn('discard spam: data %s' % data) - return response.text('BAD_REQUEST', status=400) + abort(400) processor.enqueue({'request': 'new_comment', 'data': data}) except: logger.exception("new comment failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" @app.route("/report", methods=['GET']) -def report(request): +def report(): try: token = request.args.get('token', '') @@ -126,25 +100,25 @@ def report(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) site = Site.select().where(Site.token == token).get() if site is None: logger.warn('Unknown site %s' % token) - return response.text('', status=404) + abort(404) processor.enqueue({'request': 'report', 'data': token}) except: logger.exception("report failure") - return response.text('ERROR', status=500) + abort(500) - return response.text('OK') + return "OK" @app.route("/accept", methods=['GET']) -def accept_comment(request): +def accept_comment(): try: id = request.args.get('comment', '') @@ -152,19 +126,19 @@ def accept_comment(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) processor.enqueue({'request': 'late_accept', 'data': id}) except: logger.exception("accept failure") - return response.text('', status=500) + abort(500) - return response.text('PUBLISHED') + return "PUBLISHED" @app.route("/reject", methods=['GET']) -def reject_comment(request): +def reject_comment(): try: id = request.args.get('comment', '') @@ -172,12 +146,12 @@ def reject_comment(request): if secret != config.SECRET: logger.warn('Unauthorized request') - return response.text('UNAUTHORIZED', status=401) + abort(401) processor.enqueue({'request': 'late_reject', 'data': id}) except: logger.exception("reject failure") - return response.text('ERROR', status=500) + abort(500) - return response.text('REJECTED') + return "REJECTED" diff --git a/app/controllers/mail.py b/app/controllers/mail.py index 0c7ba87..7ade3fc 100644 --- a/app/controllers/mail.py +++ b/app/controllers/mail.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from sanic import response +from flask import request, abort from app import app from app.services import processor @@ -10,16 +10,16 @@ logger = logging.getLogger(__name__) @app.route("/inbox", methods=['POST']) -def new_mail(request): +def new_mail(): try: - data = request.json + data = request.get_json() logger.debug(data) processor.enqueue({'request': 'new_mail', 'data': data}) except: logger.exception("new mail failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" diff --git a/app/controllers/reader.py b/app/controllers/reader.py index 0631861..2673105 100644 --- a/app/controllers/reader.py +++ b/app/controllers/reader.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import logging -from sanic import response +from flask import request, abort from app import app from app.services import processor @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) @app.route("/unsubscribe", methods=['GET']) -def unsubscribe(request): +def unsubscribe(): try: data = { @@ -24,6 +24,6 @@ def unsubscribe(request): except: logger.exception("unsubscribe failure") - return response.text('BAD_REQUEST', status=400) + abort(400) - return response.text('OK') + return "OK" diff --git a/app/run.py b/app/run.py index 5c821a0..fc8bd1a 100644 --- a/app/run.py +++ b/app/run.py @@ -4,6 +4,8 @@ import os import sys import logging +from werkzeug.contrib.fixers import ProxyFix +from flask.ext.cors import CORS # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -15,8 +17,6 @@ for path in paths: # more imports import config -from sanic_cors import CORS, cross_origin - from app.services import database from app.services import processor from app.controllers import api @@ -59,16 +59,17 @@ processor.start(template_path) logger.info("Start Stacosys application") # enable CORS -cors = CORS(app, resources=r'/comments/*') +cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING +app.wsgi_app = ProxyFix(app.wsgi_app) + if __name__ == '__main__': + app.run(host=config.HTTP_ADDRESS, port=config.HTTP_PORT, - debug=config.DEBUG, - log_config=None, - workers=config.HTTP_WORKERS) + debug=config.DEBUG, use_reloader=False) diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/processor.py b/app/services/processor.py index 7bba82d..2c18536 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -356,9 +356,9 @@ def rss(token, onstart=False): for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by( -Comment.published).limit(10): - item_link = "http://%s%s" % (site.url, row.url) + item_link = "%s://%s%s" % (config.RSS_URL_PROTO, site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - http://%s%s' % (row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.RSS_URL_PROTO, row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -367,7 +367,7 @@ def rss(token, onstart=False): rss = PyRSS2Gen.RSS2( title=rss_title, - link="http://" + site.url, + link='%s://%s' % (config.RSS_URL_PROTO, site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), items=items) diff --git a/config.py b/config.py index 4d969dc..16f780a 100644 --- a/config.py +++ b/config.py @@ -10,7 +10,7 @@ DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "0.0.0.0" -HTTP_PORT = 8000 +HTTP_PORT = 8100 HTTP_WORKERS = 1 CORS_ORIGIN = "*" @@ -20,4 +20,5 @@ SECRET = "Uqca5Kc8xuU6THz9" ROOT_URL = 'http://localhost:8000' +RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index 16a1cc4..dd09c42 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -47,7 +47,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - //fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) + fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -80,6 +80,7 @@ func main() { json.Unmarshal(file, &config) fmt.Printf("config: %s\n", string(file)) + http.HandleFunc("/comments/count/", commentsCount) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) } diff --git a/requirements.txt b/requirements.txt index a06b7da..cc8994e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ -aiofiles==0.3.1 +click==6.7 clize==2.4 -hiredis==0.2.0 -httptools==0.0.9 +Flask==0.12.2 +Flask-Cors==3.0.3 +itsdangerous==0.24 Jinja2==2.7.3 Markdown==2.6.2 MarkupSafe==0.23 @@ -9,9 +10,5 @@ peewee==2.6.0 PyMySQL==0.6.6 PyRSS2Gen==1.1 requests==2.7.0 -sanic==0.5.4 -Sanic-Cors==0.5.4.2 six==1.9.0 -ujson==1.35 -uvloop==0.8.0 -websockets==3.3 +Werkzeug==0.12.2 From ffdfe3985c227c798d02def443c8c71a8a7f82e8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 16 Jul 2017 18:12:39 +0200 Subject: [PATCH 175/586] remove traces --- go-http/httpfastcache.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index dd09c42..df48e8b 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -47,7 +47,6 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -62,7 +61,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // cache body and return response countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - fmt.Printf(string(body)) + //fmt.Printf(string(body)) w.Write(body) } @@ -80,7 +79,7 @@ func main() { json.Unmarshal(file, &config) fmt.Printf("config: %s\n", string(file)) - http.HandleFunc("/comments/count/", commentsCount) + //http.HandleFunc("/comments/count/", commentsCount) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) } From 885996f25cac2d8bfaaa9f80c8af450badd7eafa Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 16 Jul 2017 18:12:39 +0200 Subject: [PATCH 176/586] remove traces --- go-http/httpfastcache.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go index dd09c42..df48e8b 100644 --- a/go-http/httpfastcache.go +++ b/go-http/httpfastcache.go @@ -47,7 +47,6 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { } // relay request to stacosys - fmt.Println("QUERY: " + config.Stacosys + r.URL.String()) response, err := http.Get(config.Stacosys + r.URL.String()) if err != nil { http.NotFound(w, r) @@ -62,7 +61,7 @@ func commentsCount(w http.ResponseWriter, r *http.Request) { // cache body and return response countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - fmt.Printf(string(body)) + //fmt.Printf(string(body)) w.Write(body) } @@ -80,7 +79,7 @@ func main() { json.Unmarshal(file, &config) fmt.Printf("config: %s\n", string(file)) - http.HandleFunc("/comments/count/", commentsCount) + //http.HandleFunc("/comments/count/", commentsCount) http.HandleFunc("/comments/count", commentsCount) http.ListenAndServe(config.HostPort, nil) } From eecea8c66105d96240ca134459e275c920a9ddeb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Nov 2017 07:33:21 +0100 Subject: [PATCH 177/586] Add HTML form entry point --- app/controllers/api.py | 66 ------------------------------ app/controllers/form.py | 41 +++++++++++++++++++ app/controllers/report.py | 78 +++++++++++++++++++++++++++++++++++ app/run.py | 2 + go-http/config.json | 5 --- go-http/httpfastcache.go | 85 --------------------------------------- requirements.txt | 18 +++++---- 7 files changed, 131 insertions(+), 164 deletions(-) create mode 100644 app/controllers/form.py create mode 100644 app/controllers/report.py delete mode 100644 go-http/config.json delete mode 100644 go-http/httpfastcache.go diff --git a/app/controllers/api.py b/app/controllers/api.py index 1879b6b..0facc42 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -89,69 +89,3 @@ def new_comment(): abort(400) return "OK" - - -@app.route("/report", methods=['GET']) -def report(): - - try: - token = request.args.get('token', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(404) - - processor.enqueue({'request': 'report', 'data': token}) - - - except: - logger.exception("report failure") - abort(500) - - return "OK" - - -@app.route("/accept", methods=['GET']) -def accept_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_accept', 'data': id}) - - except: - logger.exception("accept failure") - abort(500) - - return "PUBLISHED" - - -@app.route("/reject", methods=['GET']) -def reject_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_reject', 'data': id}) - - except: - logger.exception("reject failure") - abort(500) - - return "REJECTED" diff --git a/app/controllers/form.py b/app/controllers/form.py new file mode 100644 index 0000000..3733793 --- /dev/null +++ b/app/controllers/form.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import config +from flask import request, jsonify, abort +from app import app +from app.models.site import Site +from app.models.comment import Comment +from app.helpers.hashing import md5 +from app.services import processor + +logger = logging.getLogger(__name__) + +@app.route("/newcomment", methods=['POST']) +def new_form_comment(): + + try: + data = request.form + logger.info(data) + + # validate token: retrieve site entity + token = data.get('token', '') + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(400) + + # honeypot for spammers + captcha = data.get('captcha', '') + if captcha: + logger.warn('discard spam: data %s' % data) + abort(400) + + processor.enqueue({'request': 'new_comment', 'data': data}) + + except: + logger.exception("new comment failure") + abort(400) + + return "OK" \ No newline at end of file diff --git a/app/controllers/report.py b/app/controllers/report.py new file mode 100644 index 0000000..c3467fd --- /dev/null +++ b/app/controllers/report.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import config +from flask import request, jsonify, abort +from app import app +from app.models.site import Site +from app.models.comment import Comment +from app.helpers.hashing import md5 +from app.services import processor + +logger = logging.getLogger(__name__) + +@app.route("/report", methods=['GET']) +def report(): + + try: + token = request.args.get('token', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(404) + + processor.enqueue({'request': 'report', 'data': token}) + + + except: + logger.exception("report failure") + abort(500) + + return "OK" + + +@app.route("/accept", methods=['GET']) +def accept_comment(): + + try: + id = request.args.get('comment', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_accept', 'data': id}) + + except: + logger.exception("accept failure") + abort(500) + + return "PUBLISHED" + + +@app.route("/reject", methods=['GET']) +def reject_comment(): + + try: + id = request.args.get('comment', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_reject', 'data': id}) + + except: + logger.exception("reject failure") + abort(500) + + return "REJECTED" diff --git a/app/run.py b/app/run.py index fc8bd1a..073cea2 100644 --- a/app/run.py +++ b/app/run.py @@ -20,6 +20,8 @@ import config from app.services import database from app.services import processor from app.controllers import api +from app.controllers import form +from app.controllers import report from app.controllers import mail from app.controllers import reader from app import app diff --git a/go-http/config.json b/go-http/config.json deleted file mode 100644 index 61e7b52..0000000 --- a/go-http/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "HostPort":"127.0.0.1:8101", - "Stacosys":"http://127.0.0.1:8100", - "CorsOrigin":"blogduyax.madyanne.fr" -} diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go deleted file mode 100644 index df48e8b..0000000 --- a/go-http/httpfastcache.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/patrickmn/go-cache" - "io/ioutil" - "net/http" - "os" - "time" -) - -// ConfigType represents config info -type ConfigType struct { - HostPort string - Stacosys string - CorsOrigin string -} - -var config ConfigType -var countCache = cache.New(5*time.Minute, 10*time.Minute) - -func die(format string, v ...interface{}) { - fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...)) - os.Exit(1) -} - -func commentsCount(w http.ResponseWriter, r *http.Request) { - - // only GET method is supported - if r.Method != "GET" { - http.NotFound(w, r) - return - } - - // set header - w.Header().Add("Content-Type", "application/json") - w.Header().Add("Access-Control-Allow-Origin", config.CorsOrigin) - - // get cached value - cachedBody, found := countCache.Get(r.URL.String()) - if found { - //fmt.Printf("return cached value") - w.Write([]byte(cachedBody.(string))) - return - } - - // relay request to stacosys - response, err := http.Get(config.Stacosys + r.URL.String()) - if err != nil { - http.NotFound(w, r) - return - } - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - http.NotFound(w, r) - return - } - - // cache body and return response - countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - //fmt.Printf(string(body)) - w.Write(body) -} - -func main() { - pathname := flag.String("config", "", "config pathname") - flag.Parse() - if *pathname == "" { - die("%s --config ", os.Args[0]) - } - // read config File - file, e := ioutil.ReadFile(*pathname) - if e != nil { - die("File error: %v", e) - } - json.Unmarshal(file, &config) - fmt.Printf("config: %s\n", string(file)) - - //http.HandleFunc("/comments/count/", commentsCount) - http.HandleFunc("/comments/count", commentsCount) - http.ListenAndServe(config.HostPort, nil) -} diff --git a/requirements.txt b/requirements.txt index cc8994e..311f04f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,16 @@ +certifi==2017.11.5 +chardet==3.0.4 click==6.7 -clize==2.4 Flask==0.12.2 Flask-Cors==3.0.3 +idna==2.6 itsdangerous==0.24 -Jinja2==2.7.3 -Markdown==2.6.2 -MarkupSafe==0.23 -peewee==2.6.0 -PyMySQL==0.6.6 +Jinja2==2.10 +Markdown==2.6.9 +MarkupSafe==1.0 +peewee==2.10.2 PyRSS2Gen==1.1 -requests==2.7.0 -six==1.9.0 +requests==2.18.4 +six==1.11.0 +urllib3==1.22 Werkzeug==0.12.2 From 04ce4dfbb7de607ef2ea7053d04192364d479dda Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 11 Nov 2017 07:33:21 +0100 Subject: [PATCH 178/586] Add HTML form entry point --- app/controllers/api.py | 66 ------------------------------ app/controllers/form.py | 41 +++++++++++++++++++ app/controllers/report.py | 78 +++++++++++++++++++++++++++++++++++ app/run.py | 2 + go-http/config.json | 5 --- go-http/httpfastcache.go | 85 --------------------------------------- requirements.txt | 18 +++++---- 7 files changed, 131 insertions(+), 164 deletions(-) create mode 100644 app/controllers/form.py create mode 100644 app/controllers/report.py delete mode 100644 go-http/config.json delete mode 100644 go-http/httpfastcache.go diff --git a/app/controllers/api.py b/app/controllers/api.py index 1879b6b..0facc42 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -89,69 +89,3 @@ def new_comment(): abort(400) return "OK" - - -@app.route("/report", methods=['GET']) -def report(): - - try: - token = request.args.get('token', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(404) - - processor.enqueue({'request': 'report', 'data': token}) - - - except: - logger.exception("report failure") - abort(500) - - return "OK" - - -@app.route("/accept", methods=['GET']) -def accept_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_accept', 'data': id}) - - except: - logger.exception("accept failure") - abort(500) - - return "PUBLISHED" - - -@app.route("/reject", methods=['GET']) -def reject_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.SECRET: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_reject', 'data': id}) - - except: - logger.exception("reject failure") - abort(500) - - return "REJECTED" diff --git a/app/controllers/form.py b/app/controllers/form.py new file mode 100644 index 0000000..3733793 --- /dev/null +++ b/app/controllers/form.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import config +from flask import request, jsonify, abort +from app import app +from app.models.site import Site +from app.models.comment import Comment +from app.helpers.hashing import md5 +from app.services import processor + +logger = logging.getLogger(__name__) + +@app.route("/newcomment", methods=['POST']) +def new_form_comment(): + + try: + data = request.form + logger.info(data) + + # validate token: retrieve site entity + token = data.get('token', '') + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(400) + + # honeypot for spammers + captcha = data.get('captcha', '') + if captcha: + logger.warn('discard spam: data %s' % data) + abort(400) + + processor.enqueue({'request': 'new_comment', 'data': data}) + + except: + logger.exception("new comment failure") + abort(400) + + return "OK" \ No newline at end of file diff --git a/app/controllers/report.py b/app/controllers/report.py new file mode 100644 index 0000000..c3467fd --- /dev/null +++ b/app/controllers/report.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import config +from flask import request, jsonify, abort +from app import app +from app.models.site import Site +from app.models.comment import Comment +from app.helpers.hashing import md5 +from app.services import processor + +logger = logging.getLogger(__name__) + +@app.route("/report", methods=['GET']) +def report(): + + try: + token = request.args.get('token', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + site = Site.select().where(Site.token == token).get() + if site is None: + logger.warn('Unknown site %s' % token) + abort(404) + + processor.enqueue({'request': 'report', 'data': token}) + + + except: + logger.exception("report failure") + abort(500) + + return "OK" + + +@app.route("/accept", methods=['GET']) +def accept_comment(): + + try: + id = request.args.get('comment', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_accept', 'data': id}) + + except: + logger.exception("accept failure") + abort(500) + + return "PUBLISHED" + + +@app.route("/reject", methods=['GET']) +def reject_comment(): + + try: + id = request.args.get('comment', '') + secret = request.args.get('secret', '') + + if secret != config.SECRET: + logger.warn('Unauthorized request') + abort(401) + + processor.enqueue({'request': 'late_reject', 'data': id}) + + except: + logger.exception("reject failure") + abort(500) + + return "REJECTED" diff --git a/app/run.py b/app/run.py index fc8bd1a..073cea2 100644 --- a/app/run.py +++ b/app/run.py @@ -20,6 +20,8 @@ import config from app.services import database from app.services import processor from app.controllers import api +from app.controllers import form +from app.controllers import report from app.controllers import mail from app.controllers import reader from app import app diff --git a/go-http/config.json b/go-http/config.json deleted file mode 100644 index 61e7b52..0000000 --- a/go-http/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "HostPort":"127.0.0.1:8101", - "Stacosys":"http://127.0.0.1:8100", - "CorsOrigin":"blogduyax.madyanne.fr" -} diff --git a/go-http/httpfastcache.go b/go-http/httpfastcache.go deleted file mode 100644 index df48e8b..0000000 --- a/go-http/httpfastcache.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/patrickmn/go-cache" - "io/ioutil" - "net/http" - "os" - "time" -) - -// ConfigType represents config info -type ConfigType struct { - HostPort string - Stacosys string - CorsOrigin string -} - -var config ConfigType -var countCache = cache.New(5*time.Minute, 10*time.Minute) - -func die(format string, v ...interface{}) { - fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...)) - os.Exit(1) -} - -func commentsCount(w http.ResponseWriter, r *http.Request) { - - // only GET method is supported - if r.Method != "GET" { - http.NotFound(w, r) - return - } - - // set header - w.Header().Add("Content-Type", "application/json") - w.Header().Add("Access-Control-Allow-Origin", config.CorsOrigin) - - // get cached value - cachedBody, found := countCache.Get(r.URL.String()) - if found { - //fmt.Printf("return cached value") - w.Write([]byte(cachedBody.(string))) - return - } - - // relay request to stacosys - response, err := http.Get(config.Stacosys + r.URL.String()) - if err != nil { - http.NotFound(w, r) - return - } - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - http.NotFound(w, r) - return - } - - // cache body and return response - countCache.Set(r.URL.String(), string(body), cache.DefaultExpiration) - //fmt.Printf(string(body)) - w.Write(body) -} - -func main() { - pathname := flag.String("config", "", "config pathname") - flag.Parse() - if *pathname == "" { - die("%s --config ", os.Args[0]) - } - // read config File - file, e := ioutil.ReadFile(*pathname) - if e != nil { - die("File error: %v", e) - } - json.Unmarshal(file, &config) - fmt.Printf("config: %s\n", string(file)) - - //http.HandleFunc("/comments/count/", commentsCount) - http.HandleFunc("/comments/count", commentsCount) - http.ListenAndServe(config.HostPort, nil) -} diff --git a/requirements.txt b/requirements.txt index cc8994e..311f04f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,16 @@ +certifi==2017.11.5 +chardet==3.0.4 click==6.7 -clize==2.4 Flask==0.12.2 Flask-Cors==3.0.3 +idna==2.6 itsdangerous==0.24 -Jinja2==2.7.3 -Markdown==2.6.2 -MarkupSafe==0.23 -peewee==2.6.0 -PyMySQL==0.6.6 +Jinja2==2.10 +Markdown==2.6.9 +MarkupSafe==1.0 +peewee==2.10.2 PyRSS2Gen==1.1 -requests==2.7.0 -six==1.9.0 +requests==2.18.4 +six==1.11.0 +urllib3==1.22 Werkzeug==0.12.2 From 3c3ede6840f01f86dea0d8b014d133ede094d7b7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Nov 2017 08:44:28 +0100 Subject: [PATCH 179/586] redirect --- app/controllers/form.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 3733793..9892c68 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -3,7 +3,7 @@ import logging import config -from flask import request, jsonify, abort +from flask import request, jsonify, abort, redirect from app import app from app.models.site import Site from app.models.comment import Comment @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return "OK" \ No newline at end of file + return redirect('/redirect', code=302) \ No newline at end of file From 26808631ac3990d34d60621764a9afd69c4d11ba Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 11 Nov 2017 08:44:28 +0100 Subject: [PATCH 180/586] redirect --- app/controllers/form.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 3733793..9892c68 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -3,7 +3,7 @@ import logging import config -from flask import request, jsonify, abort +from flask import request, jsonify, abort, redirect from app import app from app.models.site import Site from app.models.comment import Comment @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return "OK" \ No newline at end of file + return redirect('/redirect', code=302) \ No newline at end of file From cff35798a763bc353458bf0f0cc0d4effc1a4a03 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 12 Nov 2017 18:20:05 +0100 Subject: [PATCH 181/586] upd config --- config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 16f780a..04ac873 100644 --- a/config.py +++ b/config.py @@ -9,7 +9,7 @@ DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" -HTTP_ADDRESS = "0.0.0.0" +HTTP_ADDRESS = "127.0.0.1" HTTP_PORT = 8100 HTTP_WORKERS = 1 CORS_ORIGIN = "*" @@ -18,7 +18,7 @@ SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" SECRET = "Uqca5Kc8xuU6THz9" -ROOT_URL = 'http://localhost:8000' +ROOT_URL = 'http://localhost:8100' RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' From 5316a3771bca1bc971a1a53ed1a6541c447aceae Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 12 Nov 2017 18:20:05 +0100 Subject: [PATCH 182/586] upd config --- config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 16f780a..04ac873 100644 --- a/config.py +++ b/config.py @@ -9,7 +9,7 @@ DB_URL = "sqlite:///db.sqlite" MAIL_URL = "http://localhost:8025/mbox" -HTTP_ADDRESS = "0.0.0.0" +HTTP_ADDRESS = "127.0.0.1" HTTP_PORT = 8100 HTTP_WORKERS = 1 CORS_ORIGIN = "*" @@ -18,7 +18,7 @@ SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" SECRET = "Uqca5Kc8xuU6THz9" -ROOT_URL = 'http://localhost:8000' +ROOT_URL = 'http://localhost:8100' RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' From fdc2bcecc19d6cdf9f94fc6301057fe5e3a7ba7b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 18 Nov 2017 18:27:39 +0100 Subject: [PATCH 183/586] Fix redirect --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 9892c68..dbcf714 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect', code=302) \ No newline at end of file + return redirect('/redirect/', code=302) \ No newline at end of file From f2581445d26023b2932bb8fa8cf9e69444f68688 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 18 Nov 2017 18:27:39 +0100 Subject: [PATCH 184/586] Fix redirect --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 9892c68..dbcf714 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect', code=302) \ No newline at end of file + return redirect('/redirect/', code=302) \ No newline at end of file From 749fb326107036d6eea84c500be535f3f21f314b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 18 Nov 2017 18:36:37 +0100 Subject: [PATCH 185/586] rollback redirect URL change --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index dbcf714..9892c68 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) \ No newline at end of file + return redirect('/redirect', code=302) \ No newline at end of file From d7f8933f7756f3030877c700219208532bae3b52 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 18 Nov 2017 18:36:37 +0100 Subject: [PATCH 186/586] rollback redirect URL change --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index dbcf714..9892c68 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) \ No newline at end of file + return redirect('/redirect', code=302) \ No newline at end of file From 7718776ae80bb5fe629675e243d90f5129caa352 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 18 Nov 2017 18:38:54 +0100 Subject: [PATCH 187/586] Undo --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 9892c68..dbcf714 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect', code=302) \ No newline at end of file + return redirect('/redirect/', code=302) \ No newline at end of file From 3e15b83d2ac1c9f4515b127a778bcfb77f136a21 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 18 Nov 2017 18:38:54 +0100 Subject: [PATCH 188/586] Undo --- app/controllers/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/form.py b/app/controllers/form.py index 9892c68..dbcf714 100644 --- a/app/controllers/form.py +++ b/app/controllers/form.py @@ -38,4 +38,4 @@ def new_form_comment(): logger.exception("new comment failure") abort(400) - return redirect('/redirect', code=302) \ No newline at end of file + return redirect('/redirect/', code=302) \ No newline at end of file From 34f10baae22ce96f50c70d97cb97e9425db6de19 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Dec 2017 13:40:24 +0100 Subject: [PATCH 189/586] add private mode --- app/controllers/api.py | 4 +--- app/models/comment.py | 1 + app/run.py | 24 ++++++++++++++++-------- app/services/processor.py | 27 ++++++++++++++++++--------- config.py | 3 ++- 5 files changed, 38 insertions(+), 21 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0facc42..e03591d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -7,7 +7,6 @@ from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment -from app.helpers.hashing import md5 from app.services import processor logger = logging.getLogger(__name__) @@ -31,8 +30,7 @@ def query_comments(): d['content'] = comment.content if comment.author_site: d['site'] = comment.author_site - if comment.author_email: - d['avatar'] = md5(comment.author_email.strip().lower()) + d['avatar'] = comment.author_gravatar d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) diff --git a/app/models/comment.py b/app/models/comment.py index 1492085..9ebdfb7 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -17,6 +17,7 @@ class Comment(Model): author_name = CharField() author_email = CharField(default='') author_site = CharField(default='') + author_gravatar = CharField(default='') content = TextField() site = ForeignKeyField(Site, related_name='site') diff --git a/app/run.py b/app/run.py index 073cea2..1494fc9 100644 --- a/app/run.py +++ b/app/run.py @@ -4,7 +4,6 @@ import os import sys import logging -from werkzeug.contrib.fixers import ProxyFix from flask.ext.cors import CORS # add current path and parent path to syspath @@ -23,7 +22,6 @@ from app.controllers import api from app.controllers import form from app.controllers import report from app.controllers import mail -from app.controllers import reader from app import app @@ -49,26 +47,36 @@ logger = logging.getLogger(__name__) # initialize database database.setup() +#from app.helpers.hashing import md5 +#from app.models.comment import Comment +#for comment in Comment.select(): +# email = comment.author_email.strip().lower() +# if email: +# comment.author_gravatar = md5(email) +# comment.author_email = '' +# comment.save() + # routes logger.debug('imported: %s ' % api.__name__) logger.debug('imported: %s ' % mail.__name__) -logger.debug('imported: %s ' % reader.__name__) # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) -logger.info("Start Stacosys application") - -# enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +# less feature in private mode +if not config.PRIVATE: + # enable CORS + cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) + from app.controllers import reader + logger.debug('imported: %s ' % reader.__name__) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.wsgi_app = ProxyFix(app.wsgi_app) +logger.info("Start Stacosys application") if __name__ == '__main__': diff --git a/app/services/processor.py b/app/services/processor.py index 2c18536..c2af6a8 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,9 +10,9 @@ from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader from app.models.site import Site -from app.models.comment import Comment from app.models.reader import Reader from app.models.report import Report +from app.helpers.hashing import md5 import requests import json import config @@ -68,6 +68,13 @@ def new_comment(data): message = data.get('message', '') subscribe = data.get('subscribe', '') + # private mode: email contains gravar md5 hash + if config.PRIVATE: + author_gravatar = author_email + author_email = '' + else: + author_gravatar = md5(author_email.lower()) + # create a new comment row site = Site.select().where(Site.token == token).get() @@ -79,6 +86,7 @@ def new_comment(data): # add a row to Comment table comment = Comment(site=site, url=url, author_name=author_name, author_site=author_site, author_email=author_email, + author_gravatar=author_gravatar, content=message, created=created, published=None) comment.save() @@ -104,7 +112,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe and author_email: + if not config.PRIVATE and subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") @@ -163,14 +171,15 @@ def reply_comment_email(data): mail(from_email, 'Re: ' + subject, email_body) # notify reader once comment is published - reader_email = get_email_metadata(message) - if reader_email: - notify_reader(from_email, reader_email, comment.site.token, - comment.site.url, comment.url) + if not config.PRIVATE: + reader_email = get_email_metadata(message) + if reader_email: + notify_reader(from_email, reader_email, comment.site.token, + comment.site.url, comment.url) - # notify subscribers every time a new comment is published - notify_subscribed_readers( - comment.site.token, comment.site.url, comment.url) + # notify subscribers every time a new comment is published + notify_subscribed_readers( + comment.site.token, comment.site.url, comment.url) def late_reject_comment(id): diff --git a/config.py b/config.py index 04ac873..219e3e7 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,6 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "127.0.0.1" HTTP_PORT = 8100 HTTP_WORKERS = 1 -CORS_ORIGIN = "*" SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" @@ -22,3 +21,5 @@ ROOT_URL = 'http://localhost:8100' RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' + +PRIVATE = True \ No newline at end of file From f05bfb8383219d16557976ab4d40f3fd5718acf6 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Dec 2017 13:40:24 +0100 Subject: [PATCH 190/586] add private mode --- app/controllers/api.py | 4 +--- app/models/comment.py | 1 + app/run.py | 24 ++++++++++++++++-------- app/services/processor.py | 27 ++++++++++++++++++--------- config.py | 3 ++- 5 files changed, 38 insertions(+), 21 deletions(-) diff --git a/app/controllers/api.py b/app/controllers/api.py index 0facc42..e03591d 100644 --- a/app/controllers/api.py +++ b/app/controllers/api.py @@ -7,7 +7,6 @@ from flask import request, jsonify, abort from app import app from app.models.site import Site from app.models.comment import Comment -from app.helpers.hashing import md5 from app.services import processor logger = logging.getLogger(__name__) @@ -31,8 +30,7 @@ def query_comments(): d['content'] = comment.content if comment.author_site: d['site'] = comment.author_site - if comment.author_email: - d['avatar'] = md5(comment.author_email.strip().lower()) + d['avatar'] = comment.author_gravatar d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) diff --git a/app/models/comment.py b/app/models/comment.py index 1492085..9ebdfb7 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -17,6 +17,7 @@ class Comment(Model): author_name = CharField() author_email = CharField(default='') author_site = CharField(default='') + author_gravatar = CharField(default='') content = TextField() site = ForeignKeyField(Site, related_name='site') diff --git a/app/run.py b/app/run.py index 073cea2..1494fc9 100644 --- a/app/run.py +++ b/app/run.py @@ -4,7 +4,6 @@ import os import sys import logging -from werkzeug.contrib.fixers import ProxyFix from flask.ext.cors import CORS # add current path and parent path to syspath @@ -23,7 +22,6 @@ from app.controllers import api from app.controllers import form from app.controllers import report from app.controllers import mail -from app.controllers import reader from app import app @@ -49,26 +47,36 @@ logger = logging.getLogger(__name__) # initialize database database.setup() +#from app.helpers.hashing import md5 +#from app.models.comment import Comment +#for comment in Comment.select(): +# email = comment.author_email.strip().lower() +# if email: +# comment.author_gravatar = md5(email) +# comment.author_email = '' +# comment.save() + # routes logger.debug('imported: %s ' % api.__name__) logger.debug('imported: %s ' % mail.__name__) -logger.debug('imported: %s ' % reader.__name__) # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) processor.start(template_path) -logger.info("Start Stacosys application") - -# enable CORS -cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) +# less feature in private mode +if not config.PRIVATE: + # enable CORS + cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) + from app.controllers import reader + logger.debug('imported: %s ' % reader.__name__) # tune logging level if not config.DEBUG: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING -app.wsgi_app = ProxyFix(app.wsgi_app) +logger.info("Start Stacosys application") if __name__ == '__main__': diff --git a/app/services/processor.py b/app/services/processor.py index 2c18536..c2af6a8 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -10,9 +10,9 @@ from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader from app.models.site import Site -from app.models.comment import Comment from app.models.reader import Reader from app.models.report import Report +from app.helpers.hashing import md5 import requests import json import config @@ -68,6 +68,13 @@ def new_comment(data): message = data.get('message', '') subscribe = data.get('subscribe', '') + # private mode: email contains gravar md5 hash + if config.PRIVATE: + author_gravatar = author_email + author_email = '' + else: + author_gravatar = md5(author_email.lower()) + # create a new comment row site = Site.select().where(Site.token == token).get() @@ -79,6 +86,7 @@ def new_comment(data): # add a row to Comment table comment = Comment(site=site, url=url, author_name=author_name, author_site=author_site, author_email=author_email, + author_gravatar=author_gravatar, content=message, created=created, published=None) comment.save() @@ -104,7 +112,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if subscribe and author_email: + if not config.PRIVATE and subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") @@ -163,14 +171,15 @@ def reply_comment_email(data): mail(from_email, 'Re: ' + subject, email_body) # notify reader once comment is published - reader_email = get_email_metadata(message) - if reader_email: - notify_reader(from_email, reader_email, comment.site.token, - comment.site.url, comment.url) + if not config.PRIVATE: + reader_email = get_email_metadata(message) + if reader_email: + notify_reader(from_email, reader_email, comment.site.token, + comment.site.url, comment.url) - # notify subscribers every time a new comment is published - notify_subscribed_readers( - comment.site.token, comment.site.url, comment.url) + # notify subscribers every time a new comment is published + notify_subscribed_readers( + comment.site.token, comment.site.url, comment.url) def late_reject_comment(id): diff --git a/config.py b/config.py index 04ac873..219e3e7 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,6 @@ MAIL_URL = "http://localhost:8025/mbox" HTTP_ADDRESS = "127.0.0.1" HTTP_PORT = 8100 HTTP_WORKERS = 1 -CORS_ORIGIN = "*" SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" @@ -22,3 +21,5 @@ ROOT_URL = 'http://localhost:8100' RSS_URL_PROTO = 'http' RSS_FILE = 'comments.xml' + +PRIVATE = True \ No newline at end of file From 901b2247ade094c279541362cae4f921f91a8bd6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Dec 2017 13:51:34 +0100 Subject: [PATCH 191/586] Fix import error --- app/services/processor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/processor.py b/app/services/processor.py index c2af6a8..c92a814 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -12,6 +12,7 @@ from jinja2 import FileSystemLoader from app.models.site import Site from app.models.reader import Reader from app.models.report import Report +from app.models.report import Comment from app.helpers.hashing import md5 import requests import json From e9ad960971f969bc88258666385d03b2398d8fb9 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Dec 2017 13:51:34 +0100 Subject: [PATCH 192/586] Fix import error --- app/services/processor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/processor.py b/app/services/processor.py index c2af6a8..c92a814 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -12,6 +12,7 @@ from jinja2 import FileSystemLoader from app.models.site import Site from app.models.reader import Reader from app.models.report import Report +from app.models.report import Comment from app.helpers.hashing import md5 import requests import json From dd7ab75460ceb56db7eb68dd2fcbc052456fa033 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Dec 2017 13:57:15 +0100 Subject: [PATCH 193/586] Fix import --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index c92a814..ea496f1 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -12,7 +12,7 @@ from jinja2 import FileSystemLoader from app.models.site import Site from app.models.reader import Reader from app.models.report import Report -from app.models.report import Comment +from app.models.comment import Comment from app.helpers.hashing import md5 import requests import json From 8c5d4d7301a368f108cdb956f4a6df3c0e66d9a6 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Dec 2017 13:57:15 +0100 Subject: [PATCH 194/586] Fix import --- app/services/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/processor.py b/app/services/processor.py index c92a814..ea496f1 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -12,7 +12,7 @@ from jinja2 import FileSystemLoader from app.models.site import Site from app.models.reader import Reader from app.models.report import Report -from app.models.report import Comment +from app.models.comment import Comment from app.helpers.hashing import md5 import requests import json From 2c5b63fcf5b58607a5f7c2f459d22bb5c87db40a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 16 Jan 2018 18:49:19 +0100 Subject: [PATCH 195/586] fetch emails through Zero MQ --- app/conf/config.py | 6 ++++ app/{controllers => interface}/api.py | 0 app/{controllers => interface}/form.py | 0 app/{controllers => interface}/mail.py | 0 app/{controllers => interface}/reader.py | 0 app/{controllers => interface}/report.py | 0 app/interface/zclient.py | 43 ++++++++++++++++++++++++ app/run.py | 25 +++++--------- app/services/processor.py | 4 +-- requirements.txt | 11 ++---- 10 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 app/conf/config.py rename app/{controllers => interface}/api.py (100%) rename app/{controllers => interface}/form.py (100%) rename app/{controllers => interface}/mail.py (100%) rename app/{controllers => interface}/reader.py (100%) rename app/{controllers => interface}/report.py (100%) create mode 100644 app/interface/zclient.py diff --git a/app/conf/config.py b/app/conf/config.py new file mode 100644 index 0000000..7ca283f --- /dev/null +++ b/app/conf/config.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# TODO move to JSON config + +zmq = {'pub_port': 7701, 'sub_port':7702} \ No newline at end of file diff --git a/app/controllers/api.py b/app/interface/api.py similarity index 100% rename from app/controllers/api.py rename to app/interface/api.py diff --git a/app/controllers/form.py b/app/interface/form.py similarity index 100% rename from app/controllers/form.py rename to app/interface/form.py diff --git a/app/controllers/mail.py b/app/interface/mail.py similarity index 100% rename from app/controllers/mail.py rename to app/interface/mail.py diff --git a/app/controllers/reader.py b/app/interface/reader.py similarity index 100% rename from app/controllers/reader.py rename to app/interface/reader.py diff --git a/app/controllers/report.py b/app/interface/report.py similarity index 100% rename from app/controllers/report.py rename to app/interface/report.py diff --git a/app/interface/zclient.py b/app/interface/zclient.py new file mode 100644 index 0000000..69bfb8b --- /dev/null +++ b/app/interface/zclient.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import zmq +from conf import config +from threading import Thread +import logging +import json +from app.services import processor + +logger = logging.getLogger(__name__) + +context = zmq.Context() + + +def process(message): + data = json.loads(message) + if data['topic'] == 'email:newmail': + logger.info('newmail => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + + +class Consumer(Thread): + + def run(self): + zsub = context.socket(zmq.SUB) + zsub.connect('tcp://127.0.0.1:{}'.format(config.zmq['pub_port'])) + zsub.setsockopt_string(zmq.SUBSCRIBE, '') + self.loop = True + while self.loop: + message = zsub.recv() + try: + process(message) + except: + logger.exception('cannot process broker message') + + def stop(self): + self.loop = False + + +def start(): + c = Consumer() + c.start() diff --git a/app/run.py b/app/run.py index 1494fc9..746596f 100644 --- a/app/run.py +++ b/app/run.py @@ -18,10 +18,11 @@ for path in paths: import config from app.services import database from app.services import processor -from app.controllers import api -from app.controllers import form -from app.controllers import report -from app.controllers import mail +from app.interface import api +from app.interface import form +from app.interface import report +#from app.controllers import mail +from app.interface import zclient from app import app @@ -45,20 +46,10 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) # initialize database -database.setup() +database.setup() -#from app.helpers.hashing import md5 -#from app.models.comment import Comment -#for comment in Comment.select(): -# email = comment.author_email.strip().lower() -# if email: -# comment.author_gravatar = md5(email) -# comment.author_email = '' -# comment.save() - -# routes -logger.debug('imported: %s ' % api.__name__) -logger.debug('imported: %s ' % mail.__name__) +# start broker client +zclient.start() # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) diff --git a/app/services/processor.py b/app/services/processor.py index ea496f1..7fbecb3 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -14,7 +14,6 @@ from app.models.reader import Reader from app.models.report import Report from app.models.comment import Comment from app.helpers.hashing import md5 -import requests import json import config import PyRSS2Gen @@ -392,7 +391,8 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) + # do something smart here + # r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) if r.status_code in (200, 201): logger.debug('Email for %s posted' % to_email) else: diff --git a/requirements.txt b/requirements.txt index 311f04f..33c72d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,11 @@ -certifi==2017.11.5 chardet==3.0.4 click==6.7 Flask==0.12.2 -Flask-Cors==3.0.3 -idna==2.6 itsdangerous==0.24 Jinja2==2.10 -Markdown==2.6.9 +Markdown==2.6.11 MarkupSafe==1.0 peewee==2.10.2 PyRSS2Gen==1.1 -requests==2.18.4 -six==1.11.0 -urllib3==1.22 -Werkzeug==0.12.2 +pyzmq==16.0.3 +Werkzeug==0.14.1 From 47bda266a4cdf7c60f1e6d8a8646237ebf65c578 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 16 Jan 2018 18:49:19 +0100 Subject: [PATCH 196/586] fetch emails through Zero MQ --- app/conf/config.py | 6 ++++ app/{controllers => interface}/api.py | 0 app/{controllers => interface}/form.py | 0 app/{controllers => interface}/mail.py | 0 app/{controllers => interface}/reader.py | 0 app/{controllers => interface}/report.py | 0 app/interface/zclient.py | 43 ++++++++++++++++++++++++ app/run.py | 25 +++++--------- app/services/processor.py | 4 +-- requirements.txt | 11 ++---- 10 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 app/conf/config.py rename app/{controllers => interface}/api.py (100%) rename app/{controllers => interface}/form.py (100%) rename app/{controllers => interface}/mail.py (100%) rename app/{controllers => interface}/reader.py (100%) rename app/{controllers => interface}/report.py (100%) create mode 100644 app/interface/zclient.py diff --git a/app/conf/config.py b/app/conf/config.py new file mode 100644 index 0000000..7ca283f --- /dev/null +++ b/app/conf/config.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# TODO move to JSON config + +zmq = {'pub_port': 7701, 'sub_port':7702} \ No newline at end of file diff --git a/app/controllers/api.py b/app/interface/api.py similarity index 100% rename from app/controllers/api.py rename to app/interface/api.py diff --git a/app/controllers/form.py b/app/interface/form.py similarity index 100% rename from app/controllers/form.py rename to app/interface/form.py diff --git a/app/controllers/mail.py b/app/interface/mail.py similarity index 100% rename from app/controllers/mail.py rename to app/interface/mail.py diff --git a/app/controllers/reader.py b/app/interface/reader.py similarity index 100% rename from app/controllers/reader.py rename to app/interface/reader.py diff --git a/app/controllers/report.py b/app/interface/report.py similarity index 100% rename from app/controllers/report.py rename to app/interface/report.py diff --git a/app/interface/zclient.py b/app/interface/zclient.py new file mode 100644 index 0000000..69bfb8b --- /dev/null +++ b/app/interface/zclient.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import zmq +from conf import config +from threading import Thread +import logging +import json +from app.services import processor + +logger = logging.getLogger(__name__) + +context = zmq.Context() + + +def process(message): + data = json.loads(message) + if data['topic'] == 'email:newmail': + logger.info('newmail => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + + +class Consumer(Thread): + + def run(self): + zsub = context.socket(zmq.SUB) + zsub.connect('tcp://127.0.0.1:{}'.format(config.zmq['pub_port'])) + zsub.setsockopt_string(zmq.SUBSCRIBE, '') + self.loop = True + while self.loop: + message = zsub.recv() + try: + process(message) + except: + logger.exception('cannot process broker message') + + def stop(self): + self.loop = False + + +def start(): + c = Consumer() + c.start() diff --git a/app/run.py b/app/run.py index 1494fc9..746596f 100644 --- a/app/run.py +++ b/app/run.py @@ -18,10 +18,11 @@ for path in paths: import config from app.services import database from app.services import processor -from app.controllers import api -from app.controllers import form -from app.controllers import report -from app.controllers import mail +from app.interface import api +from app.interface import form +from app.interface import report +#from app.controllers import mail +from app.interface import zclient from app import app @@ -45,20 +46,10 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) # initialize database -database.setup() +database.setup() -#from app.helpers.hashing import md5 -#from app.models.comment import Comment -#for comment in Comment.select(): -# email = comment.author_email.strip().lower() -# if email: -# comment.author_gravatar = md5(email) -# comment.author_email = '' -# comment.save() - -# routes -logger.debug('imported: %s ' % api.__name__) -logger.debug('imported: %s ' % mail.__name__) +# start broker client +zclient.start() # start processor template_path = os.path.abspath(os.path.join(current_path, 'templates')) diff --git a/app/services/processor.py b/app/services/processor.py index ea496f1..7fbecb3 100644 --- a/app/services/processor.py +++ b/app/services/processor.py @@ -14,7 +14,6 @@ from app.models.reader import Reader from app.models.report import Report from app.models.comment import Comment from app.helpers.hashing import md5 -import requests import json import config import PyRSS2Gen @@ -392,7 +391,8 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) + # do something smart here + # r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) if r.status_code in (200, 201): logger.debug('Email for %s posted' % to_email) else: diff --git a/requirements.txt b/requirements.txt index 311f04f..33c72d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,11 @@ -certifi==2017.11.5 chardet==3.0.4 click==6.7 Flask==0.12.2 -Flask-Cors==3.0.3 -idna==2.6 itsdangerous==0.24 Jinja2==2.10 -Markdown==2.6.9 +Markdown==2.6.11 MarkupSafe==1.0 peewee==2.10.2 PyRSS2Gen==1.1 -requests==2.18.4 -six==1.11.0 -urllib3==1.22 -Werkzeug==0.12.2 +pyzmq==16.0.3 +Werkzeug==0.14.1 From 754c37a373d8100068c5ae940fbb34d305c3d46a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 20 Jan 2018 08:57:49 +0100 Subject: [PATCH 197/586] Configuration moved to JSON and validated by JSON Schema --- app/{services => conf}/__init__.py | 0 app/conf/schema.py | 147 ++++++++++++++++++++++++++++ app/{run.py => core/__init__.py} | 37 ++++--- app/{services => core}/database.py | 12 +-- app/{services => core}/processor.py | 42 ++++---- app/helpers/hashing.py | 4 +- app/interface/api.py | 9 +- app/interface/form.py | 11 +-- app/interface/mail.py | 4 +- app/interface/reader.py | 4 +- app/interface/report.py | 18 ++-- app/interface/zclient.py | 2 +- app/models/comment.py | 4 +- app/models/reader.py | 4 +- app/models/report.py | 4 +- app/models/site.py | 2 +- app/stacosys.py | 37 +++++++ config.json | 27 +++++ config.py | 25 ----- requirements.txt | 8 ++ run.sh | 2 +- 21 files changed, 300 insertions(+), 103 deletions(-) rename app/{services => conf}/__init__.py (100%) create mode 100644 app/conf/schema.py rename app/{run.py => core/__init__.py} (71%) rename app/{services => core}/database.py (63%) rename app/{services => core}/processor.py (91%) create mode 100644 app/stacosys.py create mode 100644 config.json delete mode 100644 config.py diff --git a/app/services/__init__.py b/app/conf/__init__.py similarity index 100% rename from app/services/__init__.py rename to app/conf/__init__.py diff --git a/app/conf/schema.py b/app/conf/schema.py new file mode 100644 index 0000000..f76d18a --- /dev/null +++ b/app/conf/schema.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Created with https://app.quicktype.io +# name: stacosys + +json_schema = """ +{ + "$ref": "#/definitions/Stacosys", + "definitions": { + "Stacosys": { + "type": "object", + "additionalProperties": false, + "properties": { + "general": { + "$ref": "#/definitions/General" + }, + "http": { + "$ref": "#/definitions/HTTP" + }, + "security": { + "$ref": "#/definitions/Security" + }, + "rss": { + "$ref": "#/definitions/RSS" + }, + "zmq": { + "$ref": "#/definitions/Zmq" + } + }, + "required": [ + "general", + "http", + "rss", + "security", + "zmq" + ], + "title": "stacosys" + }, + "General": { + "type": "object", + "additionalProperties": false, + "properties": { + "debug": { + "type": "boolean" + }, + "lang": { + "type": "string" + }, + "db_url": { + "type": "string" + } + }, + "required": [ + "db_url", + "debug", + "lang" + ], + "title": "general" + }, + "HTTP": { + "type": "object", + "additionalProperties": false, + "properties": { + "root_url": { + "type": "string" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "required": [ + "host", + "port", + "root_url" + ], + "title": "http" + }, + "RSS": { + "type": "object", + "additionalProperties": false, + "properties": { + "proto": { + "type": "string" + }, + "file": { + "type": "string" + } + }, + "required": [ + "file", + "proto" + ], + "title": "rss" + }, + "Security": { + "type": "object", + "additionalProperties": false, + "properties": { + "salt": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "required": [ + "private", + "salt", + "secret" + ], + "title": "security" + }, + "Zmq": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "pub_port": { + "type": "integer" + }, + "sub_port": { + "type": "integer" + } + }, + "required": [ + "active", + "host", + "pub_port", + "sub_port" + ], + "title": "zmq" + } + } +} +""" \ No newline at end of file diff --git a/app/run.py b/app/core/__init__.py similarity index 71% rename from app/run.py rename to app/core/__init__.py index 746596f..a85d304 100644 --- a/app/run.py +++ b/app/core/__init__.py @@ -4,7 +4,12 @@ import os import sys import logging +from flask import Flask from flask.ext.cors import CORS +from conf import config +from jsonschema import validate + +app = Flask(__name__) # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -15,16 +20,12 @@ for path in paths: sys.path.insert(0, path) # more imports -import config -from app.services import database -from app.services import processor -from app.interface import api -from app.interface import form -from app.interface import report -#from app.controllers import mail -from app.interface import zclient -from app import app - +import database +import processor +from interface import api +from interface import form +from interface import report +from interface import zclient # configure logging def configure_logging(level): @@ -40,7 +41,7 @@ def configure_logging(level): # add ch to logger root_logger.addHandler(ch) -logging_level = (20, 10)[config.DEBUG] +logging_level = (20, 10)[config.general['debug']] configure_logging(logging_level) logger = logging.getLogger(__name__) @@ -52,25 +53,23 @@ database.setup() zclient.start() # start processor -template_path = os.path.abspath(os.path.join(current_path, 'templates')) +template_path = os.path.abspath(os.path.join(current_path, '../templates')) processor.start(template_path) # less feature in private mode -if not config.PRIVATE: +if not config.security['private']: # enable CORS cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) from app.controllers import reader logger.debug('imported: %s ' % reader.__name__) # tune logging level -if not config.DEBUG: +if not config.general['debug']: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING logger.info("Start Stacosys application") -if __name__ == '__main__': - - app.run(host=config.HTTP_ADDRESS, - port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) +app.run(host=config.http['host'], + port=config.http['port'], + debug=config.general['debug'], use_reloader=False) \ No newline at end of file diff --git a/app/services/database.py b/app/core/database.py similarity index 63% rename from app/services/database.py rename to app/core/database.py index 0075e13..7f96f5d 100644 --- a/app/services/database.py +++ b/app/core/database.py @@ -1,13 +1,13 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import config +from conf import config import functools from playhouse.db_url import connect def get_db(): - return connect(config.DB_URL) + return connect(config.general['db_url']) def provide_db(func): @@ -21,9 +21,9 @@ def provide_db(func): @provide_db def setup(db): - from app.models.site import Site - from app.models.comment import Comment - from app.models.reader import Reader - from app.models.report import Report + from models.site import Site + from models.comment import Comment + from models.reader import Reader + from models.report import Report db.create_tables([Site, Comment, Reader, Report], safe=True) diff --git a/app/services/processor.py b/app/core/processor.py similarity index 91% rename from app/services/processor.py rename to app/core/processor.py index 7fbecb3..f75b986 100644 --- a/app/services/processor.py +++ b/app/core/processor.py @@ -9,21 +9,27 @@ from threading import Thread from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader -from app.models.site import Site -from app.models.reader import Reader -from app.models.report import Report -from app.models.comment import Comment -from app.helpers.hashing import md5 +from models.site import Site +from models.reader import Reader +from models.report import Report +from models.comment import Comment +from helpers.hashing import md5 import json -import config +from conf import config import PyRSS2Gen import markdown +import zmq logger = logging.getLogger(__name__) queue = Queue() proc = None env = None +if config.zmq['active']: + context = zmq.Context() + zpub = context.socket(zmq.PUB) + zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) + class Processor(Thread): @@ -69,7 +75,7 @@ def new_comment(data): subscribe = data.get('subscribe', '') # private mode: email contains gravar md5 hash - if config.PRIVATE: + if config.security['private']: author_gravatar = author_email author_email = '' else: @@ -112,7 +118,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if not config.PRIVATE and subscribe and author_email: + if not config.security['private'] and subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") @@ -171,7 +177,7 @@ def reply_comment_email(data): mail(from_email, 'Re: ' + subject, email_body) # notify reader once comment is published - if not config.PRIVATE: + if not config.security['private']: reader_email = get_email_metadata(message) if reader_email: notify_reader(from_email, reader_email, comment.site.token, @@ -258,7 +264,7 @@ def notify_subscribed_readers(token, site_url, url): to_email = reader.email logger.info('notify reader %s' % to_email) unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( - config.ROOT_URL, to_email, token, reader.url) + config.http['root_url'], to_email, token, reader.url) email_body = get_template( 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) @@ -337,8 +343,8 @@ def report(token): unsubscribed.append({'url': "http://" + site.url + row.url, 'name': row.name, 'email': row.email}) - email_body = get_template('report').render(secret=config.SECRET, - root_url=config.ROOT_URL, + email_body = get_template('report').render(secret=config.security['secret'], + root_url=config.http['root_url'], standbys=standbys, published=published, rejected=rejected, @@ -354,7 +360,7 @@ def report(token): def rss(token, onstart=False): - if onstart and os.path.isfile(config.RSS_FILE): + if onstart and os.path.isfile(config.rss['file']): return site = Site.select().where(Site.token == token).get() @@ -365,9 +371,9 @@ def rss(token, onstart=False): for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by( -Comment.published).limit(10): - item_link = "%s://%s%s" % (config.RSS_URL_PROTO, site.url, row.url) + item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.RSS_URL_PROTO, row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.rss['proto'], row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -376,11 +382,11 @@ def rss(token, onstart=False): rss = PyRSS2Gen.RSS2( title=rss_title, - link='%s://%s' % (config.RSS_URL_PROTO, site.url), + link='%s://%s' % (config.rss['proto'], site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), items=items) - rss.write_xml(open(config.RSS_FILE, "w"), encoding="utf-8") + rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') def mail(to_email, subject, message): @@ -400,7 +406,7 @@ def mail(to_email, subject, message): def get_template(name): - return env.get_template(config.LANG + '/' + name + '.tpl') + return env.get_template(config.general['lang'] + '/' + name + '.tpl') def enqueue(something): diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py index 2d91635..207da43 100644 --- a/app/helpers/hashing.py +++ b/app/helpers/hashing.py @@ -2,11 +2,11 @@ # -*- coding: UTF-8 -*- import hashlib -import config +from conf import config def salt(value): - string = '%s%s' % (value, config.SALT) + string = '%s%s' % (value, config.security['salt']) dk = hashlib.sha256(string.encode()) return dk.hexdigest() diff --git a/app/interface/api.py b/app/interface/api.py index e03591d..db35e22 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -2,12 +2,11 @@ # -*- coding: utf-8 -*- import logging -import config from flask import request, jsonify, abort -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/form.py b/app/interface/form.py index dbcf714..9998b2c 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -2,13 +2,12 @@ # -*- coding: utf-8 -*- import logging -import config from flask import request, jsonify, abort, redirect -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.helpers.hashing import md5 -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from helpers.hashing import md5 +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/mail.py b/app/interface/mail.py index 7ade3fc..ccebbb9 100644 --- a/app/interface/mail.py +++ b/app/interface/mail.py @@ -3,8 +3,8 @@ import logging from flask import request, abort -from app import app -from app.services import processor +from core import app +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/reader.py b/app/interface/reader.py index 2673105..fb6149b 100644 --- a/app/interface/reader.py +++ b/app/interface/reader.py @@ -3,8 +3,8 @@ import logging from flask import request, abort -from app import app -from app.services import processor +from core import app +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/report.py b/app/interface/report.py index c3467fd..297b76c 100644 --- a/app/interface/report.py +++ b/app/interface/report.py @@ -2,13 +2,13 @@ # -*- coding: utf-8 -*- import logging -import config +from conf import config from flask import request, jsonify, abort -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.helpers.hashing import md5 -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from helpers.hashing import md5 +from core import processor logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ def report(): token = request.args.get('token', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) @@ -45,7 +45,7 @@ def accept_comment(): id = request.args.get('comment', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) @@ -65,7 +65,7 @@ def reject_comment(): id = request.args.get('comment', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) diff --git a/app/interface/zclient.py b/app/interface/zclient.py index 69bfb8b..a467428 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -6,7 +6,7 @@ from conf import config from threading import Thread import logging import json -from app.services import processor +from core import processor logger = logging.getLogger(__name__) diff --git a/app/models/comment.py b/app/models/comment.py index 9ebdfb7..502f0bf 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -6,8 +6,8 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from app.models.site import Site -from app.services.database import get_db +from models.site import Site +from core.database import get_db class Comment(Model): diff --git a/app/models/reader.py b/app/models/reader.py index a1eae15..212c74b 100644 --- a/app/models/reader.py +++ b/app/models/reader.py @@ -4,8 +4,8 @@ from peewee import Model from peewee import CharField from peewee import ForeignKeyField -from app.services.database import get_db -from app.models.site import Site +from core.database import get_db +from models.site import Site class Reader(Model): diff --git a/app/models/report.py b/app/models/report.py index 44e8ac0..bda6e68 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -5,8 +5,8 @@ from peewee import Model from peewee import CharField from peewee import BooleanField from peewee import ForeignKeyField -from app.services.database import get_db -from app.models.site import Site +from core.database import get_db +from models.site import Site class Report(Model): name = CharField() diff --git a/app/models/site.py b/app/models/site.py index 9c34cb2..156ebb3 100644 --- a/app/models/site.py +++ b/app/models/site.py @@ -3,7 +3,7 @@ from peewee import Model from peewee import CharField -from app.services.database import get_db +from core.database import get_db class Site(Model): diff --git a/app/stacosys.py b/app/stacosys.py new file mode 100644 index 0000000..8d26708 --- /dev/null +++ b/app/stacosys.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import logging +import json +from clize import clize, run +from jsonschema import validate +from conf import config, schema + +def load_json(filename): + jsondoc = None + with open(filename, 'rt') as json_file: + jsondoc = json.loads(json_file.read()) + return jsondoc + + +@clize +def stacosys_server(config_pathname): + + # load and validate startup config + conf = load_json(config_pathname) + json_schema = json.loads(schema.json_schema) + v = validate(conf, json_schema) + print('validation: {}'.format(v)) + + # set configuration + config.general = conf['general'] + config.http = conf['http'] + config.security = conf['security'] + config.rss = conf['rss'] + config.zmq = conf['zmq'] + + # start application + from core import app + +if __name__ == '__main__': + run(stacosys_server) \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..7b6be9e --- /dev/null +++ b/config.json @@ -0,0 +1,27 @@ +{ + "general" : { + "debug": true, + "lang": "fr", + "db_url": "sqlite:///db.sqlite" + }, + "http": { + "root_url": "http://localhost:8100", + "host": "127.0.0.1", + "port": 8100 + }, + "security": { + "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", + "secret": "Uqca5Kc8xuU6THz9", + "private": true + }, + "rss": { + "proto": "http", + "file": "comments.xml" + }, + "zmq": { + "active": true, + "host": "127.0.0.1", + "pub_port": 7701, + "sub_port": 7702 + } +} diff --git a/config.py b/config.py deleted file mode 100644 index 219e3e7..0000000 --- a/config.py +++ /dev/null @@ -1,25 +0,0 @@ -# Configuration file - -DEBUG = True - -LANG = "fr" - -# DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" -DB_URL = "sqlite:///db.sqlite" - -MAIL_URL = "http://localhost:8025/mbox" - -HTTP_ADDRESS = "127.0.0.1" -HTTP_PORT = 8100 -HTTP_WORKERS = 1 - -SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" - -SECRET = "Uqca5Kc8xuU6THz9" - -ROOT_URL = 'http://localhost:8100' - -RSS_URL_PROTO = 'http' -RSS_FILE = 'comments.xml' - -PRIVATE = True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 33c72d6..c5fd0c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,19 @@ +attrs==17.4.0 chardet==3.0.4 click==6.7 +clize==4.0.3 +docutils==0.14 Flask==0.12.2 +Flask-Cors==3.0.3 itsdangerous==0.24 Jinja2==2.10 +jsonschema==2.6.0 Markdown==2.6.11 MarkupSafe==1.0 +od==1.0 peewee==2.10.2 PyRSS2Gen==1.1 pyzmq==16.0.3 +sigtools==2.0.1 +six==1.11.0 Werkzeug==0.14.1 diff --git a/run.sh b/run.sh index a3b0364..3300b92 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/run.py "$@" +python app/stacosys.py "$@" From 8ebed3cda6f561434f8c8e6ef8ce5a7c69ece91f Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 20 Jan 2018 08:57:49 +0100 Subject: [PATCH 198/586] Configuration moved to JSON and validated by JSON Schema --- app/{services => conf}/__init__.py | 0 app/conf/schema.py | 147 ++++++++++++++++++++++++++++ app/{run.py => core/__init__.py} | 37 ++++--- app/{services => core}/database.py | 12 +-- app/{services => core}/processor.py | 42 ++++---- app/helpers/hashing.py | 4 +- app/interface/api.py | 9 +- app/interface/form.py | 11 +-- app/interface/mail.py | 4 +- app/interface/reader.py | 4 +- app/interface/report.py | 18 ++-- app/interface/zclient.py | 2 +- app/models/comment.py | 4 +- app/models/reader.py | 4 +- app/models/report.py | 4 +- app/models/site.py | 2 +- app/stacosys.py | 37 +++++++ config.json | 27 +++++ config.py | 25 ----- requirements.txt | 8 ++ run.sh | 2 +- 21 files changed, 300 insertions(+), 103 deletions(-) rename app/{services => conf}/__init__.py (100%) create mode 100644 app/conf/schema.py rename app/{run.py => core/__init__.py} (71%) rename app/{services => core}/database.py (63%) rename app/{services => core}/processor.py (91%) create mode 100644 app/stacosys.py create mode 100644 config.json delete mode 100644 config.py diff --git a/app/services/__init__.py b/app/conf/__init__.py similarity index 100% rename from app/services/__init__.py rename to app/conf/__init__.py diff --git a/app/conf/schema.py b/app/conf/schema.py new file mode 100644 index 0000000..f76d18a --- /dev/null +++ b/app/conf/schema.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Created with https://app.quicktype.io +# name: stacosys + +json_schema = """ +{ + "$ref": "#/definitions/Stacosys", + "definitions": { + "Stacosys": { + "type": "object", + "additionalProperties": false, + "properties": { + "general": { + "$ref": "#/definitions/General" + }, + "http": { + "$ref": "#/definitions/HTTP" + }, + "security": { + "$ref": "#/definitions/Security" + }, + "rss": { + "$ref": "#/definitions/RSS" + }, + "zmq": { + "$ref": "#/definitions/Zmq" + } + }, + "required": [ + "general", + "http", + "rss", + "security", + "zmq" + ], + "title": "stacosys" + }, + "General": { + "type": "object", + "additionalProperties": false, + "properties": { + "debug": { + "type": "boolean" + }, + "lang": { + "type": "string" + }, + "db_url": { + "type": "string" + } + }, + "required": [ + "db_url", + "debug", + "lang" + ], + "title": "general" + }, + "HTTP": { + "type": "object", + "additionalProperties": false, + "properties": { + "root_url": { + "type": "string" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "required": [ + "host", + "port", + "root_url" + ], + "title": "http" + }, + "RSS": { + "type": "object", + "additionalProperties": false, + "properties": { + "proto": { + "type": "string" + }, + "file": { + "type": "string" + } + }, + "required": [ + "file", + "proto" + ], + "title": "rss" + }, + "Security": { + "type": "object", + "additionalProperties": false, + "properties": { + "salt": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "required": [ + "private", + "salt", + "secret" + ], + "title": "security" + }, + "Zmq": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "pub_port": { + "type": "integer" + }, + "sub_port": { + "type": "integer" + } + }, + "required": [ + "active", + "host", + "pub_port", + "sub_port" + ], + "title": "zmq" + } + } +} +""" \ No newline at end of file diff --git a/app/run.py b/app/core/__init__.py similarity index 71% rename from app/run.py rename to app/core/__init__.py index 746596f..a85d304 100644 --- a/app/run.py +++ b/app/core/__init__.py @@ -4,7 +4,12 @@ import os import sys import logging +from flask import Flask from flask.ext.cors import CORS +from conf import config +from jsonschema import validate + +app = Flask(__name__) # add current path and parent path to syspath current_path = os.path.dirname(__file__) @@ -15,16 +20,12 @@ for path in paths: sys.path.insert(0, path) # more imports -import config -from app.services import database -from app.services import processor -from app.interface import api -from app.interface import form -from app.interface import report -#from app.controllers import mail -from app.interface import zclient -from app import app - +import database +import processor +from interface import api +from interface import form +from interface import report +from interface import zclient # configure logging def configure_logging(level): @@ -40,7 +41,7 @@ def configure_logging(level): # add ch to logger root_logger.addHandler(ch) -logging_level = (20, 10)[config.DEBUG] +logging_level = (20, 10)[config.general['debug']] configure_logging(logging_level) logger = logging.getLogger(__name__) @@ -52,25 +53,23 @@ database.setup() zclient.start() # start processor -template_path = os.path.abspath(os.path.join(current_path, 'templates')) +template_path = os.path.abspath(os.path.join(current_path, '../templates')) processor.start(template_path) # less feature in private mode -if not config.PRIVATE: +if not config.security['private']: # enable CORS cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) from app.controllers import reader logger.debug('imported: %s ' % reader.__name__) # tune logging level -if not config.DEBUG: +if not config.general['debug']: logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING logger.info("Start Stacosys application") -if __name__ == '__main__': - - app.run(host=config.HTTP_ADDRESS, - port=config.HTTP_PORT, - debug=config.DEBUG, use_reloader=False) +app.run(host=config.http['host'], + port=config.http['port'], + debug=config.general['debug'], use_reloader=False) \ No newline at end of file diff --git a/app/services/database.py b/app/core/database.py similarity index 63% rename from app/services/database.py rename to app/core/database.py index 0075e13..7f96f5d 100644 --- a/app/services/database.py +++ b/app/core/database.py @@ -1,13 +1,13 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import config +from conf import config import functools from playhouse.db_url import connect def get_db(): - return connect(config.DB_URL) + return connect(config.general['db_url']) def provide_db(func): @@ -21,9 +21,9 @@ def provide_db(func): @provide_db def setup(db): - from app.models.site import Site - from app.models.comment import Comment - from app.models.reader import Reader - from app.models.report import Report + from models.site import Site + from models.comment import Comment + from models.reader import Reader + from models.report import Report db.create_tables([Site, Comment, Reader, Report], safe=True) diff --git a/app/services/processor.py b/app/core/processor.py similarity index 91% rename from app/services/processor.py rename to app/core/processor.py index 7fbecb3..f75b986 100644 --- a/app/services/processor.py +++ b/app/core/processor.py @@ -9,21 +9,27 @@ from threading import Thread from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader -from app.models.site import Site -from app.models.reader import Reader -from app.models.report import Report -from app.models.comment import Comment -from app.helpers.hashing import md5 +from models.site import Site +from models.reader import Reader +from models.report import Report +from models.comment import Comment +from helpers.hashing import md5 import json -import config +from conf import config import PyRSS2Gen import markdown +import zmq logger = logging.getLogger(__name__) queue = Queue() proc = None env = None +if config.zmq['active']: + context = zmq.Context() + zpub = context.socket(zmq.PUB) + zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) + class Processor(Thread): @@ -69,7 +75,7 @@ def new_comment(data): subscribe = data.get('subscribe', '') # private mode: email contains gravar md5 hash - if config.PRIVATE: + if config.security['private']: author_gravatar = author_email author_email = '' else: @@ -112,7 +118,7 @@ def new_comment(data): mail(site.admin_email, subject, email_body) # Reader subscribes to further comments - if not config.PRIVATE and subscribe and author_email: + if not config.security['private'] and subscribe and author_email: subscribe_reader(author_email, token, url) logger.debug("new comment processed ") @@ -171,7 +177,7 @@ def reply_comment_email(data): mail(from_email, 'Re: ' + subject, email_body) # notify reader once comment is published - if not config.PRIVATE: + if not config.security['private']: reader_email = get_email_metadata(message) if reader_email: notify_reader(from_email, reader_email, comment.site.token, @@ -258,7 +264,7 @@ def notify_subscribed_readers(token, site_url, url): to_email = reader.email logger.info('notify reader %s' % to_email) unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( - config.ROOT_URL, to_email, token, reader.url) + config.http['root_url'], to_email, token, reader.url) email_body = get_template( 'notify_subscriber').render(article_url=article_url, unsubscribe_url=unsubscribe_url) @@ -337,8 +343,8 @@ def report(token): unsubscribed.append({'url': "http://" + site.url + row.url, 'name': row.name, 'email': row.email}) - email_body = get_template('report').render(secret=config.SECRET, - root_url=config.ROOT_URL, + email_body = get_template('report').render(secret=config.security['secret'], + root_url=config.http['root_url'], standbys=standbys, published=published, rejected=rejected, @@ -354,7 +360,7 @@ def report(token): def rss(token, onstart=False): - if onstart and os.path.isfile(config.RSS_FILE): + if onstart and os.path.isfile(config.rss['file']): return site = Site.select().where(Site.token == token).get() @@ -365,9 +371,9 @@ def rss(token, onstart=False): for row in Comment.select().join(Site).where( Site.token == token, Comment.published).order_by( -Comment.published).limit(10): - item_link = "%s://%s%s" % (config.RSS_URL_PROTO, site.url, row.url) + item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.RSS_URL_PROTO, row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.rss['proto'], row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -376,11 +382,11 @@ def rss(token, onstart=False): rss = PyRSS2Gen.RSS2( title=rss_title, - link='%s://%s' % (config.RSS_URL_PROTO, site.url), + link='%s://%s' % (config.rss['proto'], site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), items=items) - rss.write_xml(open(config.RSS_FILE, "w"), encoding="utf-8") + rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') def mail(to_email, subject, message): @@ -400,7 +406,7 @@ def mail(to_email, subject, message): def get_template(name): - return env.get_template(config.LANG + '/' + name + '.tpl') + return env.get_template(config.general['lang'] + '/' + name + '.tpl') def enqueue(something): diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py index 2d91635..207da43 100644 --- a/app/helpers/hashing.py +++ b/app/helpers/hashing.py @@ -2,11 +2,11 @@ # -*- coding: UTF-8 -*- import hashlib -import config +from conf import config def salt(value): - string = '%s%s' % (value, config.SALT) + string = '%s%s' % (value, config.security['salt']) dk = hashlib.sha256(string.encode()) return dk.hexdigest() diff --git a/app/interface/api.py b/app/interface/api.py index e03591d..db35e22 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -2,12 +2,11 @@ # -*- coding: utf-8 -*- import logging -import config from flask import request, jsonify, abort -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/form.py b/app/interface/form.py index dbcf714..9998b2c 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -2,13 +2,12 @@ # -*- coding: utf-8 -*- import logging -import config from flask import request, jsonify, abort, redirect -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.helpers.hashing import md5 -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from helpers.hashing import md5 +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/mail.py b/app/interface/mail.py index 7ade3fc..ccebbb9 100644 --- a/app/interface/mail.py +++ b/app/interface/mail.py @@ -3,8 +3,8 @@ import logging from flask import request, abort -from app import app -from app.services import processor +from core import app +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/reader.py b/app/interface/reader.py index 2673105..fb6149b 100644 --- a/app/interface/reader.py +++ b/app/interface/reader.py @@ -3,8 +3,8 @@ import logging from flask import request, abort -from app import app -from app.services import processor +from core import app +from core import processor logger = logging.getLogger(__name__) diff --git a/app/interface/report.py b/app/interface/report.py index c3467fd..297b76c 100644 --- a/app/interface/report.py +++ b/app/interface/report.py @@ -2,13 +2,13 @@ # -*- coding: utf-8 -*- import logging -import config +from conf import config from flask import request, jsonify, abort -from app import app -from app.models.site import Site -from app.models.comment import Comment -from app.helpers.hashing import md5 -from app.services import processor +from core import app +from models.site import Site +from models.comment import Comment +from helpers.hashing import md5 +from core import processor logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ def report(): token = request.args.get('token', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) @@ -45,7 +45,7 @@ def accept_comment(): id = request.args.get('comment', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) @@ -65,7 +65,7 @@ def reject_comment(): id = request.args.get('comment', '') secret = request.args.get('secret', '') - if secret != config.SECRET: + if secret != config.security['secret']: logger.warn('Unauthorized request') abort(401) diff --git a/app/interface/zclient.py b/app/interface/zclient.py index 69bfb8b..a467428 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -6,7 +6,7 @@ from conf import config from threading import Thread import logging import json -from app.services import processor +from core import processor logger = logging.getLogger(__name__) diff --git a/app/models/comment.py b/app/models/comment.py index 9ebdfb7..502f0bf 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -6,8 +6,8 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from app.models.site import Site -from app.services.database import get_db +from models.site import Site +from core.database import get_db class Comment(Model): diff --git a/app/models/reader.py b/app/models/reader.py index a1eae15..212c74b 100644 --- a/app/models/reader.py +++ b/app/models/reader.py @@ -4,8 +4,8 @@ from peewee import Model from peewee import CharField from peewee import ForeignKeyField -from app.services.database import get_db -from app.models.site import Site +from core.database import get_db +from models.site import Site class Reader(Model): diff --git a/app/models/report.py b/app/models/report.py index 44e8ac0..bda6e68 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -5,8 +5,8 @@ from peewee import Model from peewee import CharField from peewee import BooleanField from peewee import ForeignKeyField -from app.services.database import get_db -from app.models.site import Site +from core.database import get_db +from models.site import Site class Report(Model): name = CharField() diff --git a/app/models/site.py b/app/models/site.py index 9c34cb2..156ebb3 100644 --- a/app/models/site.py +++ b/app/models/site.py @@ -3,7 +3,7 @@ from peewee import Model from peewee import CharField -from app.services.database import get_db +from core.database import get_db class Site(Model): diff --git a/app/stacosys.py b/app/stacosys.py new file mode 100644 index 0000000..8d26708 --- /dev/null +++ b/app/stacosys.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import logging +import json +from clize import clize, run +from jsonschema import validate +from conf import config, schema + +def load_json(filename): + jsondoc = None + with open(filename, 'rt') as json_file: + jsondoc = json.loads(json_file.read()) + return jsondoc + + +@clize +def stacosys_server(config_pathname): + + # load and validate startup config + conf = load_json(config_pathname) + json_schema = json.loads(schema.json_schema) + v = validate(conf, json_schema) + print('validation: {}'.format(v)) + + # set configuration + config.general = conf['general'] + config.http = conf['http'] + config.security = conf['security'] + config.rss = conf['rss'] + config.zmq = conf['zmq'] + + # start application + from core import app + +if __name__ == '__main__': + run(stacosys_server) \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..7b6be9e --- /dev/null +++ b/config.json @@ -0,0 +1,27 @@ +{ + "general" : { + "debug": true, + "lang": "fr", + "db_url": "sqlite:///db.sqlite" + }, + "http": { + "root_url": "http://localhost:8100", + "host": "127.0.0.1", + "port": 8100 + }, + "security": { + "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", + "secret": "Uqca5Kc8xuU6THz9", + "private": true + }, + "rss": { + "proto": "http", + "file": "comments.xml" + }, + "zmq": { + "active": true, + "host": "127.0.0.1", + "pub_port": 7701, + "sub_port": 7702 + } +} diff --git a/config.py b/config.py deleted file mode 100644 index 219e3e7..0000000 --- a/config.py +++ /dev/null @@ -1,25 +0,0 @@ -# Configuration file - -DEBUG = True - -LANG = "fr" - -# DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys" -DB_URL = "sqlite:///db.sqlite" - -MAIL_URL = "http://localhost:8025/mbox" - -HTTP_ADDRESS = "127.0.0.1" -HTTP_PORT = 8100 -HTTP_WORKERS = 1 - -SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0" - -SECRET = "Uqca5Kc8xuU6THz9" - -ROOT_URL = 'http://localhost:8100' - -RSS_URL_PROTO = 'http' -RSS_FILE = 'comments.xml' - -PRIVATE = True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 33c72d6..c5fd0c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,19 @@ +attrs==17.4.0 chardet==3.0.4 click==6.7 +clize==4.0.3 +docutils==0.14 Flask==0.12.2 +Flask-Cors==3.0.3 itsdangerous==0.24 Jinja2==2.10 +jsonschema==2.6.0 Markdown==2.6.11 MarkupSafe==1.0 +od==1.0 peewee==2.10.2 PyRSS2Gen==1.1 pyzmq==16.0.3 +sigtools==2.0.1 +six==1.11.0 Werkzeug==0.14.1 diff --git a/run.sh b/run.sh index a3b0364..3300b92 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/run.py "$@" +python app/stacosys.py "$@" From cfeabafefb5e9dc9f06a3fd0f27ddffbc6e2172b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 20 Jan 2018 10:03:41 +0100 Subject: [PATCH 199/586] on microservice way --- app/conf/schema.py | 4 ---- app/core/processor.py | 41 +++++++++++++++++++++++++--------------- app/interface/zclient.py | 1 + app/stacosys.py | 8 ++++---- config.json | 1 - 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/app/conf/schema.py b/app/conf/schema.py index f76d18a..bcd7d15 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -121,9 +121,6 @@ json_schema = """ "type": "object", "additionalProperties": false, "properties": { - "active": { - "type": "boolean" - }, "host": { "type": "string" }, @@ -135,7 +132,6 @@ json_schema = """ } }, "required": [ - "active", "host", "pub_port", "sub_port" diff --git a/app/core/processor.py b/app/core/processor.py index f75b986..0321ee7 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -25,10 +25,10 @@ queue = Queue() proc = None env = None -if config.zmq['active']: - context = zmq.Context() - zpub = context.socket(zmq.PUB) - zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) + +context = zmq.Context() +zpub = context.socket(zmq.PUB) +zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) class Processor(Thread): @@ -78,7 +78,7 @@ def new_comment(data): if config.security['private']: author_gravatar = author_email author_email = '' - else: + else: author_gravatar = md5(author_email.lower()) # create a new comment row @@ -151,6 +151,9 @@ def reply_comment_email(data): logger.warn('ignore empty email') return + # accept email: request to delete + send_deletion_order(data) + # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': # report event @@ -344,7 +347,8 @@ def report(token): 'name': row.name, 'email': row.email}) email_body = get_template('report').render(secret=config.security['secret'], - root_url=config.http['root_url'], + root_url=config.http[ + 'root_url'], standbys=standbys, published=published, rejected=rejected, @@ -373,7 +377,8 @@ def rss(token, onstart=False): -Comment.published).limit(10): item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.rss['proto'], row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.rss['proto'], + row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -391,18 +396,24 @@ def rss(token, onstart=False): def mail(to_email, subject, message): - headers = {'Content-Type': 'application/json; charset=utf-8'} - msg = { + zmsg = { + 'topic': 'email:sendmail', 'to': to_email, 'subject': subject, 'content': message } - # do something smart here - # r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) - if r.status_code in (200, 201): - logger.debug('Email for %s posted' % to_email) - else: - logger.warn('Cannot post email for %s' % to_email) + + # TODO test broker failure and find alternative + zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + logger.debug('Email for %s posted' % to_email) + + #logger.warn('Cannot post email for %s' % to_email) + + +def send_deletion_order(zmsg): + zmsg['topic'] = 'email:delete' + zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + logger.debug('Email accepted. Deletion request sent for %s' % zmsg) def get_template(name): diff --git a/app/interface/zclient.py b/app/interface/zclient.py index a467428..63c18d7 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -39,5 +39,6 @@ class Consumer(Thread): def start(): + logger.info('start zclient') c = Consumer() c.start() diff --git a/app/stacosys.py b/app/stacosys.py index 8d26708..36d3553 100644 --- a/app/stacosys.py +++ b/app/stacosys.py @@ -7,6 +7,7 @@ from clize import clize, run from jsonschema import validate from conf import config, schema + def load_json(filename): jsondoc = None with open(filename, 'rt') as json_file: @@ -19,10 +20,9 @@ def stacosys_server(config_pathname): # load and validate startup config conf = load_json(config_pathname) - json_schema = json.loads(schema.json_schema) + json_schema = json.loads(schema.json_schema) v = validate(conf, json_schema) - print('validation: {}'.format(v)) - + # set configuration config.general = conf['general'] config.http = conf['http'] @@ -34,4 +34,4 @@ def stacosys_server(config_pathname): from core import app if __name__ == '__main__': - run(stacosys_server) \ No newline at end of file + run(stacosys_server) diff --git a/config.json b/config.json index 7b6be9e..0a2ec66 100644 --- a/config.json +++ b/config.json @@ -19,7 +19,6 @@ "file": "comments.xml" }, "zmq": { - "active": true, "host": "127.0.0.1", "pub_port": 7701, "sub_port": 7702 From cedac41b106aa4f9ad24cbf67bfd7b20a668ac09 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 20 Jan 2018 10:03:41 +0100 Subject: [PATCH 200/586] on microservice way --- app/conf/schema.py | 4 ---- app/core/processor.py | 41 +++++++++++++++++++++++++--------------- app/interface/zclient.py | 1 + app/stacosys.py | 8 ++++---- config.json | 1 - 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/app/conf/schema.py b/app/conf/schema.py index f76d18a..bcd7d15 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -121,9 +121,6 @@ json_schema = """ "type": "object", "additionalProperties": false, "properties": { - "active": { - "type": "boolean" - }, "host": { "type": "string" }, @@ -135,7 +132,6 @@ json_schema = """ } }, "required": [ - "active", "host", "pub_port", "sub_port" diff --git a/app/core/processor.py b/app/core/processor.py index f75b986..0321ee7 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -25,10 +25,10 @@ queue = Queue() proc = None env = None -if config.zmq['active']: - context = zmq.Context() - zpub = context.socket(zmq.PUB) - zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) + +context = zmq.Context() +zpub = context.socket(zmq.PUB) +zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) class Processor(Thread): @@ -78,7 +78,7 @@ def new_comment(data): if config.security['private']: author_gravatar = author_email author_email = '' - else: + else: author_gravatar = md5(author_email.lower()) # create a new comment row @@ -151,6 +151,9 @@ def reply_comment_email(data): logger.warn('ignore empty email') return + # accept email: request to delete + send_deletion_order(data) + # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': # report event @@ -344,7 +347,8 @@ def report(token): 'name': row.name, 'email': row.email}) email_body = get_template('report').render(secret=config.security['secret'], - root_url=config.http['root_url'], + root_url=config.http[ + 'root_url'], standbys=standbys, published=published, rejected=rejected, @@ -373,7 +377,8 @@ def rss(token, onstart=False): -Comment.published).limit(10): item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.rss['proto'], row.author_name, site.url, row.url), + title='%s - %s://%s%s' % (config.rss['proto'], + row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), @@ -391,18 +396,24 @@ def rss(token, onstart=False): def mail(to_email, subject, message): - headers = {'Content-Type': 'application/json; charset=utf-8'} - msg = { + zmsg = { + 'topic': 'email:sendmail', 'to': to_email, 'subject': subject, 'content': message } - # do something smart here - # r = requests.post(config.MAIL_URL, data=json.dumps(msg), headers=headers) - if r.status_code in (200, 201): - logger.debug('Email for %s posted' % to_email) - else: - logger.warn('Cannot post email for %s' % to_email) + + # TODO test broker failure and find alternative + zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + logger.debug('Email for %s posted' % to_email) + + #logger.warn('Cannot post email for %s' % to_email) + + +def send_deletion_order(zmsg): + zmsg['topic'] = 'email:delete' + zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + logger.debug('Email accepted. Deletion request sent for %s' % zmsg) def get_template(name): diff --git a/app/interface/zclient.py b/app/interface/zclient.py index a467428..63c18d7 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -39,5 +39,6 @@ class Consumer(Thread): def start(): + logger.info('start zclient') c = Consumer() c.start() diff --git a/app/stacosys.py b/app/stacosys.py index 8d26708..36d3553 100644 --- a/app/stacosys.py +++ b/app/stacosys.py @@ -7,6 +7,7 @@ from clize import clize, run from jsonschema import validate from conf import config, schema + def load_json(filename): jsondoc = None with open(filename, 'rt') as json_file: @@ -19,10 +20,9 @@ def stacosys_server(config_pathname): # load and validate startup config conf = load_json(config_pathname) - json_schema = json.loads(schema.json_schema) + json_schema = json.loads(schema.json_schema) v = validate(conf, json_schema) - print('validation: {}'.format(v)) - + # set configuration config.general = conf['general'] config.http = conf['http'] @@ -34,4 +34,4 @@ def stacosys_server(config_pathname): from core import app if __name__ == '__main__': - run(stacosys_server) \ No newline at end of file + run(stacosys_server) diff --git a/config.json b/config.json index 7b6be9e..0a2ec66 100644 --- a/config.json +++ b/config.json @@ -19,7 +19,6 @@ "file": "comments.xml" }, "zmq": { - "active": true, "host": "127.0.0.1", "pub_port": 7701, "sub_port": 7702 From 64d836d7f9a95ff54901c218dddaef300a699151 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 20 Jan 2018 11:56:14 +0100 Subject: [PATCH 201/586] Finalize ZMQ interface --- app/core/processor.py | 2 +- app/interface/zclient.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 0321ee7..3f5f2fc 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -397,7 +397,7 @@ def rss(token, onstart=False): def mail(to_email, subject, message): zmsg = { - 'topic': 'email:sendmail', + 'topic': 'email:send', 'to': to_email, 'subject': subject, 'content': message diff --git a/app/interface/zclient.py b/app/interface/zclient.py index 63c18d7..3650b79 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -15,7 +15,7 @@ context = zmq.Context() def process(message): data = json.loads(message) - if data['topic'] == 'email:newmail': + if data['topic'] == 'email:mail': logger.info('newmail => {}'.format(data)) processor.enqueue({'request': 'new_mail', 'data': data}) From feb280ed8c4a4ece5735e9cf5c35cb512eb62814 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 20 Jan 2018 11:56:14 +0100 Subject: [PATCH 202/586] Finalize ZMQ interface --- app/core/processor.py | 2 +- app/interface/zclient.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 0321ee7..3f5f2fc 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -397,7 +397,7 @@ def rss(token, onstart=False): def mail(to_email, subject, message): zmsg = { - 'topic': 'email:sendmail', + 'topic': 'email:send', 'to': to_email, 'subject': subject, 'content': message diff --git a/app/interface/zclient.py b/app/interface/zclient.py index 63c18d7..3650b79 100644 --- a/app/interface/zclient.py +++ b/app/interface/zclient.py @@ -15,7 +15,7 @@ context = zmq.Context() def process(message): data = json.loads(message) - if data['topic'] == 'email:newmail': + if data['topic'] == 'email:mail': logger.info('newmail => {}'.format(data)) processor.enqueue({'request': 'new_mail', 'data': data}) From 9d096e86cb6161d1f106577317da2a4675856be7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 27 Jan 2018 16:31:08 +0100 Subject: [PATCH 203/586] use rabbitmq --- app/conf/config.py | 3 -- app/conf/schema.py | 66 +++++++++++++++++++++++--------------- app/core/__init__.py | 4 +-- app/core/processor.py | 48 ++++++++++++++++----------- app/interface/rmqclient.py | 55 +++++++++++++++++++++++++++++++ app/interface/zclient.py | 44 ------------------------- app/stacosys.py | 2 +- config.json | 12 ++++--- requirements.txt | 2 +- 9 files changed, 138 insertions(+), 98 deletions(-) create mode 100644 app/interface/rmqclient.py delete mode 100644 app/interface/zclient.py diff --git a/app/conf/config.py b/app/conf/config.py index 7ca283f..3a1213c 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -1,6 +1,3 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# TODO move to JSON config - -zmq = {'pub_port': 7701, 'sub_port':7702} \ No newline at end of file diff --git a/app/conf/schema.py b/app/conf/schema.py index bcd7d15..8cca55f 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -24,16 +24,16 @@ json_schema = """ "rss": { "$ref": "#/definitions/RSS" }, - "zmq": { - "$ref": "#/definitions/Zmq" + "rabbitmq": { + "$ref": "#/definitions/Rabbitmq" } }, "required": [ "general", "http", + "rabbitmq", "rss", - "security", - "zmq" + "security" ], "title": "stacosys" }, @@ -79,6 +79,43 @@ json_schema = """ ], "title": "http" }, + "Rabbitmq": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "vhost": { + "type": "string" + }, + "exchange": { + "type": "string" + } + }, + "required": [ + "active", + "exchange", + "host", + "password", + "port", + "username", + "vhost" + ], + "title": "rabbitmq" + }, "RSS": { "type": "object", "additionalProperties": false, @@ -116,27 +153,6 @@ json_schema = """ "secret" ], "title": "security" - }, - "Zmq": { - "type": "object", - "additionalProperties": false, - "properties": { - "host": { - "type": "string" - }, - "pub_port": { - "type": "integer" - }, - "sub_port": { - "type": "integer" - } - }, - "required": [ - "host", - "pub_port", - "sub_port" - ], - "title": "zmq" } } } diff --git a/app/core/__init__.py b/app/core/__init__.py index a85d304..8a6cccb 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -25,7 +25,7 @@ import processor from interface import api from interface import form from interface import report -from interface import zclient +from interface import rmqclient # configure logging def configure_logging(level): @@ -50,7 +50,7 @@ logger = logging.getLogger(__name__) database.setup() # start broker client -zclient.start() +rmqclient.start() # start processor template_path = os.path.abspath(os.path.join(current_path, '../templates')) diff --git a/app/core/processor.py b/app/core/processor.py index 3f5f2fc..7c92dd4 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -18,7 +18,7 @@ import json from conf import config import PyRSS2Gen import markdown -import zmq +import pika logger = logging.getLogger(__name__) queue = Queue() @@ -26,11 +26,6 @@ proc = None env = None -context = zmq.Context() -zpub = context.socket(zmq.PUB) -zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) - - class Processor(Thread): def stop(self): @@ -142,7 +137,13 @@ def reply_comment_email(data): token = m.group(2) # retrieve site and comment rows - comment = Comment.select().where(Comment.id == comment_id).get() + try: + comment = Comment.select().where(Comment.id == comment_id).get() + except: + logger.warn('unknown comment %d' % comment_id) + send_delete_command(data) + return + if comment.site.token != token: logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return @@ -152,7 +153,7 @@ def reply_comment_email(data): return # accept email: request to delete - send_deletion_order(data) + send_delete_command(data) # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': @@ -394,26 +395,37 @@ def rss(token, onstart=False): rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') +def get_rmq_channel(): + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) + connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ + 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) + channel = connection.channel() + return channel + + def mail(to_email, subject, message): - zmsg = { - 'topic': 'email:send', + body = { 'to': to_email, 'subject': subject, 'content': message } - - # TODO test broker failure and find alternative - zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + channel = get_rmq_channel() + channel.basic_publish(exchange=config.rabbitmq['exchange'], + routing_key='mail.command.send', + body=json.dumps(body, indent=False, sort_keys=False)) logger.debug('Email for %s posted' % to_email) - #logger.warn('Cannot post email for %s' % to_email) -def send_deletion_order(zmsg): - zmsg['topic'] = 'email:delete' - zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) - logger.debug('Email accepted. Deletion request sent for %s' % zmsg) +def send_delete_command(content): + + channel = get_rmq_channel() + channel.basic_publish(exchange=config.rabbitmq['exchange'], + routing_key='mail.command.delete', + body=json.dumps(content, indent=False, sort_keys=False)) + logger.debug('Email accepted. Delete request sent for %s' % content) def get_template(name): diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py new file mode 100644 index 0000000..91857a6 --- /dev/null +++ b/app/interface/rmqclient.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pika +from conf import config +from threading import Thread +import logging +import json +from core import processor + +logger = logging.getLogger(__name__) + + +def process_message(chan, method, properties, body): + topic = method.routing_key + data = json.loads(body) + + if topic == 'mail.message': + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.warn('unsupported message [topic={}]'.format(topic)) + + +class MessageConsumer(Thread): + + def run(self): + + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) + connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ + 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) + + channel = connection.channel() + channel.exchange_declare(exchange=config.rabbitmq['exchange'], + exchange_type='topic') + + result = channel.queue_declare(exclusive=True) + queue_name = result.method.queue + channel.queue_bind(exchange=config.rabbitmq['exchange'], + queue=queue_name, + routing_key='mail.message') + channel.basic_consume(process_message, + queue=queue_name, + no_ack=True) + channel.start_consuming() + + def stop(self): + self.loop = False + + +def start(): + logger.info('start rmqclient') + c = MessageConsumer() + c.start() diff --git a/app/interface/zclient.py b/app/interface/zclient.py deleted file mode 100644 index 3650b79..0000000 --- a/app/interface/zclient.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import zmq -from conf import config -from threading import Thread -import logging -import json -from core import processor - -logger = logging.getLogger(__name__) - -context = zmq.Context() - - -def process(message): - data = json.loads(message) - if data['topic'] == 'email:mail': - logger.info('newmail => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - - -class Consumer(Thread): - - def run(self): - zsub = context.socket(zmq.SUB) - zsub.connect('tcp://127.0.0.1:{}'.format(config.zmq['pub_port'])) - zsub.setsockopt_string(zmq.SUBSCRIBE, '') - self.loop = True - while self.loop: - message = zsub.recv() - try: - process(message) - except: - logger.exception('cannot process broker message') - - def stop(self): - self.loop = False - - -def start(): - logger.info('start zclient') - c = Consumer() - c.start() diff --git a/app/stacosys.py b/app/stacosys.py index 36d3553..d1cfec0 100644 --- a/app/stacosys.py +++ b/app/stacosys.py @@ -28,7 +28,7 @@ def stacosys_server(config_pathname): config.http = conf['http'] config.security = conf['security'] config.rss = conf['rss'] - config.zmq = conf['zmq'] + config.rabbitmq = conf['rabbitmq'] # start application from core import app diff --git a/config.json b/config.json index 0a2ec66..fac65d0 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "general" : { - "debug": true, + "debug": false, "lang": "fr", "db_url": "sqlite:///db.sqlite" }, @@ -18,9 +18,13 @@ "proto": "http", "file": "comments.xml" }, - "zmq": { + "rabbitmq": { + "active": true, "host": "127.0.0.1", - "pub_port": 7701, - "sub_port": 7702 + "port": 5672, + "username": "techuser", + "password": "tech", + "vhost": "devhub", + "exchange": "hub.topic" } } diff --git a/requirements.txt b/requirements.txt index c5fd0c1..992beb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,8 +12,8 @@ Markdown==2.6.11 MarkupSafe==1.0 od==1.0 peewee==2.10.2 +pika==0.11.2 PyRSS2Gen==1.1 -pyzmq==16.0.3 sigtools==2.0.1 six==1.11.0 Werkzeug==0.14.1 From e0c9f335fc082f7713f39bb74d9d1408805726bc Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 27 Jan 2018 16:31:08 +0100 Subject: [PATCH 204/586] use rabbitmq --- app/conf/config.py | 3 -- app/conf/schema.py | 66 +++++++++++++++++++++++--------------- app/core/__init__.py | 4 +-- app/core/processor.py | 48 ++++++++++++++++----------- app/interface/rmqclient.py | 55 +++++++++++++++++++++++++++++++ app/interface/zclient.py | 44 ------------------------- app/stacosys.py | 2 +- config.json | 12 ++++--- requirements.txt | 2 +- 9 files changed, 138 insertions(+), 98 deletions(-) create mode 100644 app/interface/rmqclient.py delete mode 100644 app/interface/zclient.py diff --git a/app/conf/config.py b/app/conf/config.py index 7ca283f..3a1213c 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -1,6 +1,3 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# TODO move to JSON config - -zmq = {'pub_port': 7701, 'sub_port':7702} \ No newline at end of file diff --git a/app/conf/schema.py b/app/conf/schema.py index bcd7d15..8cca55f 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -24,16 +24,16 @@ json_schema = """ "rss": { "$ref": "#/definitions/RSS" }, - "zmq": { - "$ref": "#/definitions/Zmq" + "rabbitmq": { + "$ref": "#/definitions/Rabbitmq" } }, "required": [ "general", "http", + "rabbitmq", "rss", - "security", - "zmq" + "security" ], "title": "stacosys" }, @@ -79,6 +79,43 @@ json_schema = """ ], "title": "http" }, + "Rabbitmq": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "vhost": { + "type": "string" + }, + "exchange": { + "type": "string" + } + }, + "required": [ + "active", + "exchange", + "host", + "password", + "port", + "username", + "vhost" + ], + "title": "rabbitmq" + }, "RSS": { "type": "object", "additionalProperties": false, @@ -116,27 +153,6 @@ json_schema = """ "secret" ], "title": "security" - }, - "Zmq": { - "type": "object", - "additionalProperties": false, - "properties": { - "host": { - "type": "string" - }, - "pub_port": { - "type": "integer" - }, - "sub_port": { - "type": "integer" - } - }, - "required": [ - "host", - "pub_port", - "sub_port" - ], - "title": "zmq" } } } diff --git a/app/core/__init__.py b/app/core/__init__.py index a85d304..8a6cccb 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -25,7 +25,7 @@ import processor from interface import api from interface import form from interface import report -from interface import zclient +from interface import rmqclient # configure logging def configure_logging(level): @@ -50,7 +50,7 @@ logger = logging.getLogger(__name__) database.setup() # start broker client -zclient.start() +rmqclient.start() # start processor template_path = os.path.abspath(os.path.join(current_path, '../templates')) diff --git a/app/core/processor.py b/app/core/processor.py index 3f5f2fc..7c92dd4 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -18,7 +18,7 @@ import json from conf import config import PyRSS2Gen import markdown -import zmq +import pika logger = logging.getLogger(__name__) queue = Queue() @@ -26,11 +26,6 @@ proc = None env = None -context = zmq.Context() -zpub = context.socket(zmq.PUB) -zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port'])) - - class Processor(Thread): def stop(self): @@ -142,7 +137,13 @@ def reply_comment_email(data): token = m.group(2) # retrieve site and comment rows - comment = Comment.select().where(Comment.id == comment_id).get() + try: + comment = Comment.select().where(Comment.id == comment_id).get() + except: + logger.warn('unknown comment %d' % comment_id) + send_delete_command(data) + return + if comment.site.token != token: logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return @@ -152,7 +153,7 @@ def reply_comment_email(data): return # accept email: request to delete - send_deletion_order(data) + send_delete_command(data) # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() == 'NO': @@ -394,26 +395,37 @@ def rss(token, onstart=False): rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') +def get_rmq_channel(): + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) + connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ + 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) + channel = connection.channel() + return channel + + def mail(to_email, subject, message): - zmsg = { - 'topic': 'email:send', + body = { 'to': to_email, 'subject': subject, 'content': message } - - # TODO test broker failure and find alternative - zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) + channel = get_rmq_channel() + channel.basic_publish(exchange=config.rabbitmq['exchange'], + routing_key='mail.command.send', + body=json.dumps(body, indent=False, sort_keys=False)) logger.debug('Email for %s posted' % to_email) - #logger.warn('Cannot post email for %s' % to_email) -def send_deletion_order(zmsg): - zmsg['topic'] = 'email:delete' - zpub.send_string(json.dumps(zmsg, indent=False, sort_keys=False)) - logger.debug('Email accepted. Deletion request sent for %s' % zmsg) +def send_delete_command(content): + + channel = get_rmq_channel() + channel.basic_publish(exchange=config.rabbitmq['exchange'], + routing_key='mail.command.delete', + body=json.dumps(content, indent=False, sort_keys=False)) + logger.debug('Email accepted. Delete request sent for %s' % content) def get_template(name): diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py new file mode 100644 index 0000000..91857a6 --- /dev/null +++ b/app/interface/rmqclient.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pika +from conf import config +from threading import Thread +import logging +import json +from core import processor + +logger = logging.getLogger(__name__) + + +def process_message(chan, method, properties, body): + topic = method.routing_key + data = json.loads(body) + + if topic == 'mail.message': + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.warn('unsupported message [topic={}]'.format(topic)) + + +class MessageConsumer(Thread): + + def run(self): + + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) + connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ + 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) + + channel = connection.channel() + channel.exchange_declare(exchange=config.rabbitmq['exchange'], + exchange_type='topic') + + result = channel.queue_declare(exclusive=True) + queue_name = result.method.queue + channel.queue_bind(exchange=config.rabbitmq['exchange'], + queue=queue_name, + routing_key='mail.message') + channel.basic_consume(process_message, + queue=queue_name, + no_ack=True) + channel.start_consuming() + + def stop(self): + self.loop = False + + +def start(): + logger.info('start rmqclient') + c = MessageConsumer() + c.start() diff --git a/app/interface/zclient.py b/app/interface/zclient.py deleted file mode 100644 index 3650b79..0000000 --- a/app/interface/zclient.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import zmq -from conf import config -from threading import Thread -import logging -import json -from core import processor - -logger = logging.getLogger(__name__) - -context = zmq.Context() - - -def process(message): - data = json.loads(message) - if data['topic'] == 'email:mail': - logger.info('newmail => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - - -class Consumer(Thread): - - def run(self): - zsub = context.socket(zmq.SUB) - zsub.connect('tcp://127.0.0.1:{}'.format(config.zmq['pub_port'])) - zsub.setsockopt_string(zmq.SUBSCRIBE, '') - self.loop = True - while self.loop: - message = zsub.recv() - try: - process(message) - except: - logger.exception('cannot process broker message') - - def stop(self): - self.loop = False - - -def start(): - logger.info('start zclient') - c = Consumer() - c.start() diff --git a/app/stacosys.py b/app/stacosys.py index 36d3553..d1cfec0 100644 --- a/app/stacosys.py +++ b/app/stacosys.py @@ -28,7 +28,7 @@ def stacosys_server(config_pathname): config.http = conf['http'] config.security = conf['security'] config.rss = conf['rss'] - config.zmq = conf['zmq'] + config.rabbitmq = conf['rabbitmq'] # start application from core import app diff --git a/config.json b/config.json index 0a2ec66..fac65d0 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "general" : { - "debug": true, + "debug": false, "lang": "fr", "db_url": "sqlite:///db.sqlite" }, @@ -18,9 +18,13 @@ "proto": "http", "file": "comments.xml" }, - "zmq": { + "rabbitmq": { + "active": true, "host": "127.0.0.1", - "pub_port": 7701, - "sub_port": 7702 + "port": 5672, + "username": "techuser", + "password": "tech", + "vhost": "devhub", + "exchange": "hub.topic" } } diff --git a/requirements.txt b/requirements.txt index c5fd0c1..992beb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,8 +12,8 @@ Markdown==2.6.11 MarkupSafe==1.0 od==1.0 peewee==2.10.2 +pika==0.11.2 PyRSS2Gen==1.1 -pyzmq==16.0.3 sigtools==2.0.1 six==1.11.0 Werkzeug==0.14.1 From 86d1a8e43c29e26ededc36d1402485c156ce9674 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 27 Jan 2018 17:07:07 +0100 Subject: [PATCH 205/586] share rabbitmq conn --- app/core/processor.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 7c92dd4..dfa8c15 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -395,13 +395,12 @@ def rss(token, onstart=False): rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') -def get_rmq_channel(): +def get_rabbitmq_connection(): credentials = pika.PlainCredentials( config.rabbitmq['username'], config.rabbitmq['password']) connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - channel = connection.channel() - return channel + return connection def mail(to_email, subject, message): @@ -411,20 +410,23 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - channel = get_rmq_channel() + connection = get_rabbitmq_connection() + channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.send', body=json.dumps(body, indent=False, sort_keys=False)) + connection.close() logger.debug('Email for %s posted' % to_email) - #logger.warn('Cannot post email for %s' % to_email) def send_delete_command(content): - channel = get_rmq_channel() + connection = get_rabbitmq_connection() + channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.delete', body=json.dumps(content, indent=False, sort_keys=False)) + connection.close() logger.debug('Email accepted. Delete request sent for %s' % content) From 68408d037279b0909526c87ef099b5d7dce5d939 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 27 Jan 2018 17:07:07 +0100 Subject: [PATCH 206/586] share rabbitmq conn --- app/core/processor.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 7c92dd4..dfa8c15 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -395,13 +395,12 @@ def rss(token, onstart=False): rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') -def get_rmq_channel(): +def get_rabbitmq_connection(): credentials = pika.PlainCredentials( config.rabbitmq['username'], config.rabbitmq['password']) connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - channel = connection.channel() - return channel + return connection def mail(to_email, subject, message): @@ -411,20 +410,23 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - channel = get_rmq_channel() + connection = get_rabbitmq_connection() + channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.send', body=json.dumps(body, indent=False, sort_keys=False)) + connection.close() logger.debug('Email for %s posted' % to_email) - #logger.warn('Cannot post email for %s' % to_email) def send_delete_command(content): - channel = get_rmq_channel() + connection = get_rabbitmq_connection() + channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.delete', body=json.dumps(content, indent=False, sort_keys=False)) + connection.close() logger.debug('Email accepted. Delete request sent for %s' % content) From fd43d57db9f3ee829a5518d2bf778f1a22af98c4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 28 Jan 2018 17:53:10 +0100 Subject: [PATCH 207/586] log http headers --- app/interface/form.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/interface/form.py b/app/interface/form.py index 9998b2c..49026f6 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -16,6 +16,7 @@ def new_form_comment(): try: data = request.form + logger.info('headers: {}'.format(request.headers)) logger.info(data) # validate token: retrieve site entity From 18b879be14acf2260006a67503ef3cb7592c2a5b Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 28 Jan 2018 17:53:10 +0100 Subject: [PATCH 208/586] log http headers --- app/interface/form.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/interface/form.py b/app/interface/form.py index 9998b2c..49026f6 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -16,6 +16,7 @@ def new_form_comment(): try: data = request.form + logger.info('headers: {}'.format(request.headers)) logger.info(data) # validate token: retrieve site entity From 03ee9a1e0b381087f2d627128a7c5405181c9b10 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 28 Jan 2018 18:12:53 +0100 Subject: [PATCH 209/586] anti-spam --- app/core/processor.py | 20 +++++++++++++++++++- app/interface/form.py | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index dfa8c15..e544add 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -25,6 +25,8 @@ queue = Queue() proc = None env = None +# store client IP in memory until classification +client_ips = {} class Processor(Thread): @@ -68,6 +70,7 @@ def new_comment(data): author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') + clientip = data.get('clientip', '') # private mode: email contains gravar md5 hash if config.security['private']: @@ -108,6 +111,9 @@ def new_comment(data): email_body = get_template('new_comment').render( url=article_url, comment=comment_text) + if clientip: + client_ips[comment.ip] = clientip + # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) @@ -156,7 +162,19 @@ def reply_comment_email(data): send_delete_command(data) # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() == 'NO': + if message[:2].upper() in ('NO','SP'): + + # put a log to help fail2ban + if message[:2].upper() == 'SP': # SPAM + if comment_id in client_ips: + logger.info('SPAM comment from %s: %d' % (client_ips[comment_id], comment_id)) + else: + logger.info('cannot identify SPAM source: %d' % comment_id) + + # forget client IP + if comment_id in client_ips: + del client_ips[comment_id] + # report event report_rejected(comment) diff --git a/app/interface/form.py b/app/interface/form.py index 49026f6..1444d63 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -16,7 +16,13 @@ def new_form_comment(): try: data = request.form + + # add client IP if provided by HTTP proxy logger.info('headers: {}'.format(request.headers)) + if 'X-Forwarded-For' in request.headers: + data['clientip'] = request.headers['X-Forwarded-For'] + + # log logger.info(data) # validate token: retrieve site entity From 03b411e433040e0ef12e6c355bf95abca46804b8 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 28 Jan 2018 18:12:53 +0100 Subject: [PATCH 210/586] anti-spam --- app/core/processor.py | 20 +++++++++++++++++++- app/interface/form.py | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index dfa8c15..e544add 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -25,6 +25,8 @@ queue = Queue() proc = None env = None +# store client IP in memory until classification +client_ips = {} class Processor(Thread): @@ -68,6 +70,7 @@ def new_comment(data): author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') + clientip = data.get('clientip', '') # private mode: email contains gravar md5 hash if config.security['private']: @@ -108,6 +111,9 @@ def new_comment(data): email_body = get_template('new_comment').render( url=article_url, comment=comment_text) + if clientip: + client_ips[comment.ip] = clientip + # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) @@ -156,7 +162,19 @@ def reply_comment_email(data): send_delete_command(data) # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() == 'NO': + if message[:2].upper() in ('NO','SP'): + + # put a log to help fail2ban + if message[:2].upper() == 'SP': # SPAM + if comment_id in client_ips: + logger.info('SPAM comment from %s: %d' % (client_ips[comment_id], comment_id)) + else: + logger.info('cannot identify SPAM source: %d' % comment_id) + + # forget client IP + if comment_id in client_ips: + del client_ips[comment_id] + # report event report_rejected(comment) diff --git a/app/interface/form.py b/app/interface/form.py index 49026f6..1444d63 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -16,7 +16,13 @@ def new_form_comment(): try: data = request.form + + # add client IP if provided by HTTP proxy logger.info('headers: {}'.format(request.headers)) + if 'X-Forwarded-For' in request.headers: + data['clientip'] = request.headers['X-Forwarded-For'] + + # log logger.info(data) # validate token: retrieve site entity From 5d6574fb3796e4a178abaf4e2ac7fbc319610845 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 28 Jan 2018 18:20:42 +0100 Subject: [PATCH 211/586] fix anti-spam --- app/core/processor.py | 5 ++--- app/interface/form.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index e544add..8606eb0 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -42,7 +42,7 @@ class Processor(Thread): try: msg = queue.get() if msg['request'] == 'new_comment': - new_comment(msg['data']) + new_comment(msg['data'], msg.get('clientip', '')) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) elif msg['request'] == 'unsubscribe': @@ -59,7 +59,7 @@ class Processor(Thread): logger.exception("processing failure") -def new_comment(data): +def new_comment(data, clientip): logger.info('new comment received: %s' % data) @@ -70,7 +70,6 @@ def new_comment(data): author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') - clientip = data.get('clientip', '') # private mode: email contains gravar md5 hash if config.security['private']: diff --git a/app/interface/form.py b/app/interface/form.py index 1444d63..42d6f2c 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -18,9 +18,9 @@ def new_form_comment(): data = request.form # add client IP if provided by HTTP proxy - logger.info('headers: {}'.format(request.headers)) + clientip = '' if 'X-Forwarded-For' in request.headers: - data['clientip'] = request.headers['X-Forwarded-For'] + clientip = request.headers['X-Forwarded-For'] # log logger.info(data) @@ -38,7 +38,7 @@ def new_form_comment(): logger.warn('discard spam: data %s' % data) abort(400) - processor.enqueue({'request': 'new_comment', 'data': data}) + processor.enqueue({'request': 'new_comment', 'data': data, 'clientip': clientip}) except: logger.exception("new comment failure") From 20bcb896bcc717a9304c53bdda58882adbc3b7b9 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 28 Jan 2018 18:20:42 +0100 Subject: [PATCH 212/586] fix anti-spam --- app/core/processor.py | 5 ++--- app/interface/form.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index e544add..8606eb0 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -42,7 +42,7 @@ class Processor(Thread): try: msg = queue.get() if msg['request'] == 'new_comment': - new_comment(msg['data']) + new_comment(msg['data'], msg.get('clientip', '')) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) elif msg['request'] == 'unsubscribe': @@ -59,7 +59,7 @@ class Processor(Thread): logger.exception("processing failure") -def new_comment(data): +def new_comment(data, clientip): logger.info('new comment received: %s' % data) @@ -70,7 +70,6 @@ def new_comment(data): author_site = data.get('site', '').strip() message = data.get('message', '') subscribe = data.get('subscribe', '') - clientip = data.get('clientip', '') # private mode: email contains gravar md5 hash if config.security['private']: diff --git a/app/interface/form.py b/app/interface/form.py index 1444d63..42d6f2c 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -18,9 +18,9 @@ def new_form_comment(): data = request.form # add client IP if provided by HTTP proxy - logger.info('headers: {}'.format(request.headers)) + clientip = '' if 'X-Forwarded-For' in request.headers: - data['clientip'] = request.headers['X-Forwarded-For'] + clientip = request.headers['X-Forwarded-For'] # log logger.info(data) @@ -38,7 +38,7 @@ def new_form_comment(): logger.warn('discard spam: data %s' % data) abort(400) - processor.enqueue({'request': 'new_comment', 'data': data}) + processor.enqueue({'request': 'new_comment', 'data': data, 'clientip': clientip}) except: logger.exception("new comment failure") From eb539a8a6ffd578147d6d8405b9a80faa335b527 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 28 Jan 2018 18:22:38 +0100 Subject: [PATCH 213/586] fix --- app/core/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index 8606eb0..c8d4d28 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -111,7 +111,7 @@ def new_comment(data, clientip): url=article_url, comment=comment_text) if clientip: - client_ips[comment.ip] = clientip + client_ips[comment.id] = clientip # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) From 6cd8341a2f82271ee0b969a5cd6302cb085947e9 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 28 Jan 2018 18:22:38 +0100 Subject: [PATCH 214/586] fix --- app/core/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index 8606eb0..c8d4d28 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -111,7 +111,7 @@ def new_comment(data, clientip): url=article_url, comment=comment_text) if clientip: - client_ips[comment.ip] = clientip + client_ips[comment.id] = clientip # send email subject = '%s: [%d:%s]' % (site.name, comment.id, token) From f8ded2029de2c974790061fd7412bc34d9102c1d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 4 Feb 2018 17:44:36 +0100 Subject: [PATCH 215/586] add ping method --- app/interface/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/interface/api.py b/app/interface/api.py index db35e22..025601c 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -11,6 +11,11 @@ from core import processor logger = logging.getLogger(__name__) +@app.route("/ping", methods=['GET']) +def ping(): + return "OK" + + @app.route("/comments", methods=['GET']) def query_comments(): From 3064dca6ca27d20c5a3192c3fa92a73d6bec7765 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 4 Feb 2018 17:44:36 +0100 Subject: [PATCH 216/586] add ping method --- app/interface/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/interface/api.py b/app/interface/api.py index db35e22..025601c 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -11,6 +11,11 @@ from core import processor logger = logging.getLogger(__name__) +@app.route("/ping", methods=['GET']) +def ping(): + return "OK" + + @app.route("/comments", methods=['GET']) def query_comments(): From a654580b9e5cb8e772e3923d6723032f4b8b0122 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 11 Feb 2018 14:48:48 +0100 Subject: [PATCH 217/586] Enforce email check --- app/core/processor.py | 11 ++++++----- app/interface/rmqclient.py | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index c8d4d28..4089255 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -45,6 +45,7 @@ class Processor(Thread): new_comment(msg['data'], msg.get('clientip', '')) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) + send_delete_command(msg['data']) elif msg['request'] == 'unsubscribe': unsubscribe_reader(msg['data']) elif msg['request'] == 'report': @@ -114,7 +115,7 @@ def new_comment(data, clientip): client_ips[comment.id] = clientip # send email - subject = '%s: [%d:%s]' % (site.name, comment.id, token) + subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) # Reader subscribes to further comments @@ -146,7 +147,10 @@ def reply_comment_email(data): comment = Comment.select().where(Comment.id == comment_id).get() except: logger.warn('unknown comment %d' % comment_id) - send_delete_command(data) + return + + if comment.published: + logger.warn('ignore already published email. token %d' % comment_id) return if comment.site.token != token: @@ -157,9 +161,6 @@ def reply_comment_email(data): logger.warn('ignore empty email') return - # accept email: request to delete - send_delete_command(data) - # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() in ('NO','SP'): diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index 91857a6..b97cb58 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -12,14 +12,21 @@ logger = logging.getLogger(__name__) def process_message(chan, method, properties, body): - topic = method.routing_key - data = json.loads(body) - if topic == 'mail.message': - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) + try: + topic = method.routing_key + data = json.loads(body) + + if topic == 'mail.message': + if "STACOSYS" in data['subject']: + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.info('ignore message => {}'.format(data)) + else: + logger.warn('unsupported message [topic={}]'.format(topic)) + except: + logger.exception('cannot process message') class MessageConsumer(Thread): From ad8c1cf1156f81c84a20c7e3a37ef148d2cf90fe Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 11 Feb 2018 14:48:48 +0100 Subject: [PATCH 218/586] Enforce email check --- app/core/processor.py | 11 ++++++----- app/interface/rmqclient.py | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index c8d4d28..4089255 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -45,6 +45,7 @@ class Processor(Thread): new_comment(msg['data'], msg.get('clientip', '')) elif msg['request'] == 'new_mail': reply_comment_email(msg['data']) + send_delete_command(msg['data']) elif msg['request'] == 'unsubscribe': unsubscribe_reader(msg['data']) elif msg['request'] == 'report': @@ -114,7 +115,7 @@ def new_comment(data, clientip): client_ips[comment.id] = clientip # send email - subject = '%s: [%d:%s]' % (site.name, comment.id, token) + subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, token) mail(site.admin_email, subject, email_body) # Reader subscribes to further comments @@ -146,7 +147,10 @@ def reply_comment_email(data): comment = Comment.select().where(Comment.id == comment_id).get() except: logger.warn('unknown comment %d' % comment_id) - send_delete_command(data) + return + + if comment.published: + logger.warn('ignore already published email. token %d' % comment_id) return if comment.site.token != token: @@ -157,9 +161,6 @@ def reply_comment_email(data): logger.warn('ignore empty email') return - # accept email: request to delete - send_delete_command(data) - # safe logic: no answer or unknown answer is a go for publishing if message[:2].upper() in ('NO','SP'): diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index 91857a6..b97cb58 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -12,14 +12,21 @@ logger = logging.getLogger(__name__) def process_message(chan, method, properties, body): - topic = method.routing_key - data = json.loads(body) - if topic == 'mail.message': - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) + try: + topic = method.routing_key + data = json.loads(body) + + if topic == 'mail.message': + if "STACOSYS" in data['subject']: + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.info('ignore message => {}'.format(data)) + else: + logger.warn('unsupported message [topic={}]'.format(topic)) + except: + logger.exception('cannot process message') class MessageConsumer(Thread): From 5eda838819249a3f18171b88bb60904db9b078b7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 Jun 2018 15:25:27 +0200 Subject: [PATCH 219/586] update default config --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 config.json diff --git a/config.json b/config.json old mode 100644 new mode 100755 index fac65d0..bfd6125 --- a/config.json +++ b/config.json @@ -20,7 +20,7 @@ }, "rabbitmq": { "active": true, - "host": "127.0.0.1", + "host": "rabbit", "port": 5672, "username": "techuser", "password": "tech", From e64d4035a0198ced31dbdc85f5ef9a67cfef2230 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 Jun 2018 15:25:27 +0200 Subject: [PATCH 220/586] update default config --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 config.json diff --git a/config.json b/config.json old mode 100644 new mode 100755 index fac65d0..bfd6125 --- a/config.json +++ b/config.json @@ -20,7 +20,7 @@ }, "rabbitmq": { "active": true, - "host": "127.0.0.1", + "host": "rabbit", "port": 5672, "username": "techuser", "password": "tech", From d7b8a6bd2dedd7418170b7efe3229d9c0084a64e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 Jun 2018 15:30:25 +0200 Subject: [PATCH 221/586] update default config --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index bfd6125..1681d40 100755 --- a/config.json +++ b/config.json @@ -6,7 +6,7 @@ }, "http": { "root_url": "http://localhost:8100", - "host": "127.0.0.1", + "host": "0.0.0.0", "port": 8100 }, "security": { From afd9aea76351ea585b7c317a2bb343917e6e749e Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 Jun 2018 15:30:25 +0200 Subject: [PATCH 222/586] update default config --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index bfd6125..1681d40 100755 --- a/config.json +++ b/config.json @@ -6,7 +6,7 @@ }, "http": { "root_url": "http://localhost:8100", - "host": "127.0.0.1", + "host": "0.0.0.0", "port": 8100 }, "security": { From e12c23f0adc53fc3a7ae8774006c93a789fd9f95 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Jul 2018 18:55:40 +0200 Subject: [PATCH 223/586] system exit on comment approval --- app/core/processor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/core/processor.py b/app/core/processor.py index 4089255..7e4e31a 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import sys import os import logging import re @@ -209,6 +210,8 @@ def reply_comment_email(data): notify_subscribed_readers( comment.site.token, comment.site.url, comment.url) + # system quit + system_quit() def late_reject_comment(id): @@ -460,6 +463,10 @@ def get_processor(): return proc +def system_quit(): + sys.exit(0) + + def start(template_dir): global proc, env From d1e924d6389f43620e251e00d923aeec13d48591 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 10 Jul 2018 18:55:40 +0200 Subject: [PATCH 224/586] system exit on comment approval --- app/core/processor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/core/processor.py b/app/core/processor.py index 4089255..7e4e31a 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import sys import os import logging import re @@ -209,6 +210,8 @@ def reply_comment_email(data): notify_subscribed_readers( comment.site.token, comment.site.url, comment.url) + # system quit + system_quit() def late_reject_comment(id): @@ -460,6 +463,10 @@ def get_processor(): return proc +def system_quit(): + sys.exit(0) + + def start(template_dir): global proc, env From a38b2dfbe807c351a29997911cc539e9cc87b073 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 11 Jul 2018 18:46:04 +0200 Subject: [PATCH 225/586] revert dummy change --- app/core/processor.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 7e4e31a..fa39436 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -26,9 +26,10 @@ queue = Queue() proc = None env = None -# store client IP in memory until classification +# store client IP in memory until classification client_ips = {} + class Processor(Thread): def stop(self): @@ -163,15 +164,16 @@ def reply_comment_email(data): return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ('NO','SP'): + if message[:2].upper() in ('NO', 'SP'): - # put a log to help fail2ban - if message[:2].upper() == 'SP': # SPAM + # put a log to help fail2ban + if message[:2].upper() == 'SP': # SPAM if comment_id in client_ips: - logger.info('SPAM comment from %s: %d' % (client_ips[comment_id], comment_id)) + logger.info('SPAM comment from %s: %d' % + (client_ips[comment_id], comment_id)) else: logger.info('cannot identify SPAM source: %d' % comment_id) - + # forget client IP if comment_id in client_ips: del client_ips[comment_id] @@ -210,8 +212,6 @@ def reply_comment_email(data): notify_subscribed_readers( comment.site.token, comment.site.url, comment.url) - # system quit - system_quit() def late_reject_comment(id): @@ -463,10 +463,6 @@ def get_processor(): return proc -def system_quit(): - sys.exit(0) - - def start(template_dir): global proc, env From 8e27669e0384b9c481470bdff6e28730bedf81c9 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 11 Jul 2018 18:46:04 +0200 Subject: [PATCH 226/586] revert dummy change --- app/core/processor.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/core/processor.py b/app/core/processor.py index 7e4e31a..fa39436 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -26,9 +26,10 @@ queue = Queue() proc = None env = None -# store client IP in memory until classification +# store client IP in memory until classification client_ips = {} + class Processor(Thread): def stop(self): @@ -163,15 +164,16 @@ def reply_comment_email(data): return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ('NO','SP'): + if message[:2].upper() in ('NO', 'SP'): - # put a log to help fail2ban - if message[:2].upper() == 'SP': # SPAM + # put a log to help fail2ban + if message[:2].upper() == 'SP': # SPAM if comment_id in client_ips: - logger.info('SPAM comment from %s: %d' % (client_ips[comment_id], comment_id)) + logger.info('SPAM comment from %s: %d' % + (client_ips[comment_id], comment_id)) else: logger.info('cannot identify SPAM source: %d' % comment_id) - + # forget client IP if comment_id in client_ips: del client_ips[comment_id] @@ -210,8 +212,6 @@ def reply_comment_email(data): notify_subscribed_readers( comment.site.token, comment.site.url, comment.url) - # system quit - system_quit() def late_reject_comment(id): @@ -463,10 +463,6 @@ def get_processor(): return proc -def system_quit(): - sys.exit(0) - - def start(template_dir): global proc, env From c86b5b6e875a94fc2ba63f83002273eed7c0f752 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 11 Jul 2018 18:53:07 +0200 Subject: [PATCH 227/586] revert dummy change --- app/core/processor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index fa39436..a2c98ab 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import sys import os import logging import re From 281c10ec3f7c4ef80cb659424f5cbfad04efedc4 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 11 Jul 2018 18:53:07 +0200 Subject: [PATCH 228/586] revert dummy change --- app/core/processor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/processor.py b/app/core/processor.py index fa39436..a2c98ab 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import sys import os import logging import re From e95f59bb87f8b47e6eb6cf52d4047bdfd219ebe6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 13 Jul 2018 19:03:46 +0200 Subject: [PATCH 229/586] rabbitmq util --- app/interface/rmqclient.py | 72 ++++++++++++++------------------- app/util/rabbit.py | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 42 deletions(-) create mode 100644 app/util/rabbit.py diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index b97cb58..1edc8d2 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -7,56 +7,44 @@ from threading import Thread import logging import json from core import processor +from util import rabbit logger = logging.getLogger(__name__) +class MailConsumer(rabbit.Consumer): -def process_message(chan, method, properties, body): + def process(self, channel, method, properties, body): + try: + topic = method.routing_key + data = json.loads(body) - try: - topic = method.routing_key - data = json.loads(body) - - if topic == 'mail.message': - if "STACOSYS" in data['subject']: - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) + if topic == 'mail.message': + if "STACOSYS" in data['subject']: + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.info('ignore message => {}'.format(data)) else: - logger.info('ignore message => {}'.format(data)) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) - except: - logger.exception('cannot process message') - - -class MessageConsumer(Thread): - - def run(self): - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ - 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - - channel = connection.channel() - channel.exchange_declare(exchange=config.rabbitmq['exchange'], - exchange_type='topic') - - result = channel.queue_declare(exclusive=True) - queue_name = result.method.queue - channel.queue_bind(exchange=config.rabbitmq['exchange'], - queue=queue_name, - routing_key='mail.message') - channel.basic_consume(process_message, - queue=queue_name, - no_ack=True) - channel.start_consuming() - - def stop(self): - self.loop = False + logger.warn('unsupported message [topic={}]'.format(topic)) + except: + logger.exception('cannot process message') def start(): logger.info('start rmqclient') - c = MessageConsumer() + #c = MessageConsumer() + #c.start() + + credentials = pika.PlainCredentials(config.rabbitmq['username'], config.rabbitmq['password']) + + parameters = pika.ConnectionParameters( + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, + virtual_host=config.rabbitmq['vhost'] + ) + + connection = rabbit.Connection(parameters) + c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') c.start() + #print('exit rmqclient ' + str(c)) \ No newline at end of file diff --git a/app/util/rabbit.py b/app/util/rabbit.py new file mode 100644 index 0000000..d353d8f --- /dev/null +++ b/app/util/rabbit.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 - *- + +import logging +import pika +import time +from threading import Thread + +logger = logging.getLogger(__name__) + +EXCHANGE_TYPE = "topic" +CONNECT_DELAY = 3 + +class Connection: + + def __init__(self, connection_parameters): + self._connection_parameters = connection_parameters + + def open(self): + + self._connection = None + while True: + try: + self._connection = pika.BlockingConnection( + self._connection_parameters) + break + except: + time.sleep(CONNECT_DELAY) + logger.warn("rabbitmq connection failure. try again...") + + def close(self): + self._connection.close() + self._connection = None + + def get(self): + return self._connection + + +class Consumer(Thread): + + _connection = None + _channel = None + _queue_name = None + + def __init__(self, connection, exchange_name, routing_key): + Thread.__init__(self) + self._connection = connection + self._exchange_name = exchange_name + self._routing_key = routing_key + + def configure(self): + + self._connection = None + self._channel = None + while True: + try: + + self._channel = self._connection.channel() + self._channel.exchange_declare( + exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE + ) + + result = self._channel.queue_declare(exclusive=True) + self._queue_name = result.method.queue + self._channel.queue_bind( + exchange=self._exchange_name, + queue=self._queue_name, + routing_key=self._routing_key, + ) + break + except: + time.sleep(CONNECT_DELAY) + logger.warn("connection failure. try again...") + + def run(self): + + self.configure() + self._channel.basic_consume( + self.process, queue=self._queue_name, no_ack=True) + self._channel.start_consuming() + + def process(self, channel, method, properties, body): + raise NotImplemented From 6874f4d5ca5555dfc6bde202b9fc95e31192f5cc Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 13 Jul 2018 19:03:46 +0200 Subject: [PATCH 230/586] rabbitmq util --- app/interface/rmqclient.py | 72 ++++++++++++++------------------- app/util/rabbit.py | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 42 deletions(-) create mode 100644 app/util/rabbit.py diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index b97cb58..1edc8d2 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -7,56 +7,44 @@ from threading import Thread import logging import json from core import processor +from util import rabbit logger = logging.getLogger(__name__) +class MailConsumer(rabbit.Consumer): -def process_message(chan, method, properties, body): + def process(self, channel, method, properties, body): + try: + topic = method.routing_key + data = json.loads(body) - try: - topic = method.routing_key - data = json.loads(body) - - if topic == 'mail.message': - if "STACOSYS" in data['subject']: - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) + if topic == 'mail.message': + if "STACOSYS" in data['subject']: + logger.info('new message => {}'.format(data)) + processor.enqueue({'request': 'new_mail', 'data': data}) + else: + logger.info('ignore message => {}'.format(data)) else: - logger.info('ignore message => {}'.format(data)) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) - except: - logger.exception('cannot process message') - - -class MessageConsumer(Thread): - - def run(self): - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ - 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - - channel = connection.channel() - channel.exchange_declare(exchange=config.rabbitmq['exchange'], - exchange_type='topic') - - result = channel.queue_declare(exclusive=True) - queue_name = result.method.queue - channel.queue_bind(exchange=config.rabbitmq['exchange'], - queue=queue_name, - routing_key='mail.message') - channel.basic_consume(process_message, - queue=queue_name, - no_ack=True) - channel.start_consuming() - - def stop(self): - self.loop = False + logger.warn('unsupported message [topic={}]'.format(topic)) + except: + logger.exception('cannot process message') def start(): logger.info('start rmqclient') - c = MessageConsumer() + #c = MessageConsumer() + #c.start() + + credentials = pika.PlainCredentials(config.rabbitmq['username'], config.rabbitmq['password']) + + parameters = pika.ConnectionParameters( + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, + virtual_host=config.rabbitmq['vhost'] + ) + + connection = rabbit.Connection(parameters) + c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') c.start() + #print('exit rmqclient ' + str(c)) \ No newline at end of file diff --git a/app/util/rabbit.py b/app/util/rabbit.py new file mode 100644 index 0000000..d353d8f --- /dev/null +++ b/app/util/rabbit.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 - *- + +import logging +import pika +import time +from threading import Thread + +logger = logging.getLogger(__name__) + +EXCHANGE_TYPE = "topic" +CONNECT_DELAY = 3 + +class Connection: + + def __init__(self, connection_parameters): + self._connection_parameters = connection_parameters + + def open(self): + + self._connection = None + while True: + try: + self._connection = pika.BlockingConnection( + self._connection_parameters) + break + except: + time.sleep(CONNECT_DELAY) + logger.warn("rabbitmq connection failure. try again...") + + def close(self): + self._connection.close() + self._connection = None + + def get(self): + return self._connection + + +class Consumer(Thread): + + _connection = None + _channel = None + _queue_name = None + + def __init__(self, connection, exchange_name, routing_key): + Thread.__init__(self) + self._connection = connection + self._exchange_name = exchange_name + self._routing_key = routing_key + + def configure(self): + + self._connection = None + self._channel = None + while True: + try: + + self._channel = self._connection.channel() + self._channel.exchange_declare( + exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE + ) + + result = self._channel.queue_declare(exclusive=True) + self._queue_name = result.method.queue + self._channel.queue_bind( + exchange=self._exchange_name, + queue=self._queue_name, + routing_key=self._routing_key, + ) + break + except: + time.sleep(CONNECT_DELAY) + logger.warn("connection failure. try again...") + + def run(self): + + self.configure() + self._channel.basic_consume( + self.process, queue=self._queue_name, no_ack=True) + self._channel.start_consuming() + + def process(self, channel, method, properties, body): + raise NotImplemented From 646508b65e38e4a33a26c2ed390e92b3eee80fee Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 14 Jul 2018 12:42:59 +0200 Subject: [PATCH 231/586] rabbit connection --- app/core/processor.py | 23 +++++++++++++++-------- app/interface/rmqclient.py | 15 +++++++-------- app/{stacosys.py => run.py} | 0 app/util/rabbit.py | 19 ++++++++++--------- run.sh | 2 +- 5 files changed, 33 insertions(+), 26 deletions(-) rename app/{stacosys.py => run.py} (100%) diff --git a/app/core/processor.py b/app/core/processor.py index a2c98ab..e089f8b 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -16,6 +16,7 @@ from models.comment import Comment from helpers.hashing import md5 import json from conf import config +from util import rabbit import PyRSS2Gen import markdown import pika @@ -416,12 +417,16 @@ def rss(token, onstart=False): def get_rabbitmq_connection(): + credentials = pika.PlainCredentials( config.rabbitmq['username'], config.rabbitmq['password']) - connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ - 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - return connection - + parameters = pika.ConnectionParameters( + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, + virtual_host=config.rabbitmq['vhost'] + ) + return rabbit.Connection(parameters) def mail(to_email, subject, message): @@ -430,23 +435,25 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - connection = get_rabbitmq_connection() + connector = get_rabbitmq_connection() + connection = connector.open() channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.send', body=json.dumps(body, indent=False, sort_keys=False)) - connection.close() + connector.close() logger.debug('Email for %s posted' % to_email) def send_delete_command(content): - connection = get_rabbitmq_connection() + connector = get_rabbitmq_connection() + connection = connector.open() channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.delete', body=json.dumps(content, indent=False, sort_keys=False)) - connection.close() + connector.close() logger.debug('Email accepted. Delete request sent for %s' % content) diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index 1edc8d2..51830fb 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -11,6 +11,7 @@ from util import rabbit logger = logging.getLogger(__name__) + class MailConsumer(rabbit.Consumer): def process(self, channel, method, properties, body): @@ -31,20 +32,18 @@ class MailConsumer(rabbit.Consumer): def start(): + logger.info('start rmqclient') - #c = MessageConsumer() - #c.start() - - credentials = pika.PlainCredentials(config.rabbitmq['username'], config.rabbitmq['password']) + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, virtual_host=config.rabbitmq['vhost'] ) connection = rabbit.Connection(parameters) c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') c.start() - #print('exit rmqclient ' + str(c)) \ No newline at end of file diff --git a/app/stacosys.py b/app/run.py similarity index 100% rename from app/stacosys.py rename to app/run.py diff --git a/app/util/rabbit.py b/app/util/rabbit.py index d353d8f..042e272 100644 --- a/app/util/rabbit.py +++ b/app/util/rabbit.py @@ -26,7 +26,8 @@ class Connection: break except: time.sleep(CONNECT_DELAY) - logger.warn("rabbitmq connection failure. try again...") + logger.exception('rabbitmq connection failure. try again...') + return self._connection def close(self): self._connection.close() @@ -38,24 +39,23 @@ class Connection: class Consumer(Thread): - _connection = None + _connector = None _channel = None _queue_name = None - def __init__(self, connection, exchange_name, routing_key): + def __init__(self, connector, exchange_name, routing_key): Thread.__init__(self) - self._connection = connection + self._connector = connector self._exchange_name = exchange_name self._routing_key = routing_key - def configure(self): + def configure(self, connection): - self._connection = None self._channel = None while True: try: - self._channel = self._connection.channel() + self._channel = connection.channel() self._channel.exchange_declare( exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE ) @@ -69,12 +69,13 @@ class Consumer(Thread): ) break except: + logger.exception('configuration failure. try again...') time.sleep(CONNECT_DELAY) - logger.warn("connection failure. try again...") def run(self): - self.configure() + self._connector.open() + self.configure(self._connector.get()) self._channel.basic_consume( self.process, queue=self._queue_name, no_ack=True) self._channel.start_consuming() diff --git a/run.sh b/run.sh index 3300b92..a3b0364 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/stacosys.py "$@" +python app/run.py "$@" From 83cd8725c3b75edba6e0cd60166921fe11011482 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 14 Jul 2018 12:42:59 +0200 Subject: [PATCH 232/586] rabbit connection --- app/core/processor.py | 23 +++++++++++++++-------- app/interface/rmqclient.py | 15 +++++++-------- app/{stacosys.py => run.py} | 0 app/util/rabbit.py | 19 ++++++++++--------- run.sh | 2 +- 5 files changed, 33 insertions(+), 26 deletions(-) rename app/{stacosys.py => run.py} (100%) diff --git a/app/core/processor.py b/app/core/processor.py index a2c98ab..e089f8b 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -16,6 +16,7 @@ from models.comment import Comment from helpers.hashing import md5 import json from conf import config +from util import rabbit import PyRSS2Gen import markdown import pika @@ -416,12 +417,16 @@ def rss(token, onstart=False): def get_rabbitmq_connection(): + credentials = pika.PlainCredentials( config.rabbitmq['username'], config.rabbitmq['password']) - connection = pika.BlockingConnection(pika.ConnectionParameters(host=config.rabbitmq['host'], port=config.rabbitmq[ - 'port'], credentials=credentials, virtual_host=config.rabbitmq['vhost'])) - return connection - + parameters = pika.ConnectionParameters( + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, + virtual_host=config.rabbitmq['vhost'] + ) + return rabbit.Connection(parameters) def mail(to_email, subject, message): @@ -430,23 +435,25 @@ def mail(to_email, subject, message): 'subject': subject, 'content': message } - connection = get_rabbitmq_connection() + connector = get_rabbitmq_connection() + connection = connector.open() channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.send', body=json.dumps(body, indent=False, sort_keys=False)) - connection.close() + connector.close() logger.debug('Email for %s posted' % to_email) def send_delete_command(content): - connection = get_rabbitmq_connection() + connector = get_rabbitmq_connection() + connection = connector.open() channel = connection.channel() channel.basic_publish(exchange=config.rabbitmq['exchange'], routing_key='mail.command.delete', body=json.dumps(content, indent=False, sort_keys=False)) - connection.close() + connector.close() logger.debug('Email accepted. Delete request sent for %s' % content) diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py index 1edc8d2..51830fb 100644 --- a/app/interface/rmqclient.py +++ b/app/interface/rmqclient.py @@ -11,6 +11,7 @@ from util import rabbit logger = logging.getLogger(__name__) + class MailConsumer(rabbit.Consumer): def process(self, channel, method, properties, body): @@ -31,20 +32,18 @@ class MailConsumer(rabbit.Consumer): def start(): + logger.info('start rmqclient') - #c = MessageConsumer() - #c.start() - - credentials = pika.PlainCredentials(config.rabbitmq['username'], config.rabbitmq['password']) + credentials = pika.PlainCredentials( + config.rabbitmq['username'], config.rabbitmq['password']) parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, + host=config.rabbitmq['host'], + port=config.rabbitmq['port'], + credentials=credentials, virtual_host=config.rabbitmq['vhost'] ) connection = rabbit.Connection(parameters) c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') c.start() - #print('exit rmqclient ' + str(c)) \ No newline at end of file diff --git a/app/stacosys.py b/app/run.py similarity index 100% rename from app/stacosys.py rename to app/run.py diff --git a/app/util/rabbit.py b/app/util/rabbit.py index d353d8f..042e272 100644 --- a/app/util/rabbit.py +++ b/app/util/rabbit.py @@ -26,7 +26,8 @@ class Connection: break except: time.sleep(CONNECT_DELAY) - logger.warn("rabbitmq connection failure. try again...") + logger.exception('rabbitmq connection failure. try again...') + return self._connection def close(self): self._connection.close() @@ -38,24 +39,23 @@ class Connection: class Consumer(Thread): - _connection = None + _connector = None _channel = None _queue_name = None - def __init__(self, connection, exchange_name, routing_key): + def __init__(self, connector, exchange_name, routing_key): Thread.__init__(self) - self._connection = connection + self._connector = connector self._exchange_name = exchange_name self._routing_key = routing_key - def configure(self): + def configure(self, connection): - self._connection = None self._channel = None while True: try: - self._channel = self._connection.channel() + self._channel = connection.channel() self._channel.exchange_declare( exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE ) @@ -69,12 +69,13 @@ class Consumer(Thread): ) break except: + logger.exception('configuration failure. try again...') time.sleep(CONNECT_DELAY) - logger.warn("connection failure. try again...") def run(self): - self.configure() + self._connector.open() + self.configure(self._connector.get()) self._channel.basic_consume( self.process, queue=self._queue_name, no_ack=True) self._channel.start_consuming() diff --git a/run.sh b/run.sh index 3300b92..a3b0364 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/stacosys.py "$@" +python app/run.py "$@" From bf9346b1229c4f3fdcafaaa3e8ba534f49ea0614 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 2 Sep 2018 11:42:24 +0200 Subject: [PATCH 233/586] minimalism --- .gitignore | 4 + README.md | 16 +- app/conf/schema.py | 60 +- app/core/__init__.py | 35 +- app/core/cron.py | 47 + app/core/database.py | 4 +- app/core/mailer.py | 3 + app/core/processor.py | 407 +--- app/interface/form.py | 52 +- app/interface/mail.py | 25 - app/interface/reader.py | 29 - app/interface/report.py | 78 - app/interface/rmqclient.py | 49 - app/models/comment.py | 3 +- app/models/reader.py | 17 - app/models/report.py | 22 - app/run.py | 7 +- app/templates/en/notify_reader.tpl | 9 - app/templates/en/notify_subscriber.tpl | 13 - app/templates/en/report.tpl | 42 - app/templates/en/report_message.tpl | 1 - app/templates/en/unsubscribe_page.tpl | 2 - app/templates/fr/notify_reader.tpl | 9 - app/templates/fr/notify_subscriber.tpl | 13 - app/templates/fr/report.tpl | 42 - app/templates/fr/report_message.tpl | 1 - app/templates/fr/unsubscribe_page.tpl | 2 - app/util/rabbit.py | 84 - config.json | 12 +- demo/app.js | 14 - demo/package.json | 15 - demo/public/css/font-awesome.css | 4 - demo/public/css/grids-responsive-min.css | 7 - demo/public/css/pure-0.css | 11 - demo/public/css/style.css | 176 -- demo/public/fonts/awesome/FontAwesome.otf | Bin 75188 -> 0 bytes .../fonts/awesome/fontawesome-webfont.eot | Bin 72449 -> 0 bytes .../fonts/awesome/fontawesome-webfont.svg | 504 ----- .../fonts/awesome/fontawesome-webfont.ttf | Bin 141564 -> 0 bytes .../fonts/awesome/fontawesome-webfont.woff | Bin 83760 -> 0 bytes .../OpenSans-Regular-demo.html | 364 ---- .../OpenSans-Regular-webfont.eot | Bin 19836 -> 0 bytes .../OpenSans-Regular-webfont.svg | 1831 ----------------- .../OpenSans-Regular-webfont.ttf | Bin 38232 -> 0 bytes .../OpenSans-Regular-webfont.woff | Bin 22660 -> 0 bytes .../OpenSans-Regular-cleartype.png | Bin 84913 -> 0 bytes .../specimen_files/easytabs.js | 7 - .../specimen_files/grid_12-825-55-15.css | 129 -- .../specimen_files/specimen_stylesheet.css | 396 ---- .../opensans_regular_macroman/stylesheet.css | 12 - .../img/308a3596152a79231f3feedc49afa4ef.jpg | Bin 3618 -> 0 bytes .../img/b133b66b7edc9f7ffb5cf74a87e63652.jpg | Bin 2637 -> 0 bytes .../img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg | Bin 2669 -> 0 bytes demo/public/img/framasoft.png | Bin 19259 -> 0 bytes demo/public/img/id-150.jpg | Bin 19264 -> 0 bytes demo/public/img/license.png | Bin 5460 -> 0 bytes demo/public/img/planet-link.png | Bin 13013 -> 0 bytes demo/public/index.html | 217 -- demo/public/js/markdown.js | 1740 ---------------- demo/public/js/mustache.js | 586 ------ demo/public/js/page.js | 104 - demo/public/js/stacosys.js | 77 - demo/public/redirect.html | 64 - requirements.txt | 12 +- 64 files changed, 205 insertions(+), 7153 deletions(-) create mode 100644 app/core/cron.py create mode 100644 app/core/mailer.py delete mode 100644 app/interface/mail.py delete mode 100644 app/interface/reader.py delete mode 100644 app/interface/report.py delete mode 100644 app/interface/rmqclient.py delete mode 100644 app/models/reader.py delete mode 100644 app/models/report.py delete mode 100644 app/templates/en/notify_reader.tpl delete mode 100644 app/templates/en/notify_subscriber.tpl delete mode 100644 app/templates/en/report.tpl delete mode 100644 app/templates/en/report_message.tpl delete mode 100644 app/templates/en/unsubscribe_page.tpl delete mode 100644 app/templates/fr/notify_reader.tpl delete mode 100644 app/templates/fr/notify_subscriber.tpl delete mode 100644 app/templates/fr/report.tpl delete mode 100644 app/templates/fr/report_message.tpl delete mode 100644 app/templates/fr/unsubscribe_page.tpl delete mode 100644 app/util/rabbit.py delete mode 100644 demo/app.js delete mode 100644 demo/package.json delete mode 100644 demo/public/css/font-awesome.css delete mode 100644 demo/public/css/grids-responsive-min.css delete mode 100644 demo/public/css/pure-0.css delete mode 100644 demo/public/css/style.css delete mode 100644 demo/public/fonts/awesome/FontAwesome.otf delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.eot delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.svg delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.ttf delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.woff delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css delete mode 100644 demo/public/fonts/opensans_regular_macroman/stylesheet.css delete mode 100644 demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg delete mode 100644 demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg delete mode 100644 demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg delete mode 100644 demo/public/img/framasoft.png delete mode 100644 demo/public/img/id-150.jpg delete mode 100644 demo/public/img/license.png delete mode 100644 demo/public/img/planet-link.png delete mode 100644 demo/public/index.html delete mode 100644 demo/public/js/markdown.js delete mode 100644 demo/public/js/mustache.js delete mode 100644 demo/public/js/page.js delete mode 100644 demo/public/js/stacosys.js delete mode 100644 demo/public/redirect.html diff --git a/.gitignore b/.gitignore index 40cc63e..921016e 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ myconfig.json db.sqlite node_modules comments.xml +stacosys/bin/ +stacosys/pyvenv.cfg +stacosys/lib64 +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index db998c1..b8dffd5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an -universal way to discuss. You can answer from any device using an e-mail client or a Webmail. +universal way to discuss. You can answer from any device using an e-mail client. ### Features overview @@ -16,25 +16,17 @@ Here is the workflow: comment is submitted - Blog administrator can approve or drop the comment by replying to e-mail - Stacosys stores approved comment in its database. - -Moreover Stacosys has an additional feature: readers can subscribe to further -comments for a post if they have provided an email. Stacosys is localized (english and french). ### Technically speaking, how does it work? -Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using [CORS](http://enable-cors.org) requests. Each blog has a unique ID. Thus Stacosys can serve multiple blogs. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. ### FAQ -*So the blog needs a server-side language?* -- It depends on your hosting configuration. Stacosys can run on a different host than the blog and it can serve several blogs. You have to change JS code embedded in blog pages to point the right Stacosys API URL. - *How do you block spammers?* -- That's a huge topic. Current comment form is basic: no captcha support but a honey - pot. Nothing prevents from improving the template with JavaScript libs to do more - complex things. +- Current comment form is basic: no captcha support but a honey pot. Second defense barrier: admin can tag comment as SPAM and, for example, link stacosys log to fail2ban tool. *Which database is used?* - Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. @@ -48,4 +40,4 @@ Stacosys can be hosted on the same server or on a different server than the blog ### Ways of improvement -Current version of Stacosys fits my needs and it serves comments on [my blog](http://blogduyax.madyanne.fr) for 6 months. However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and to make e-mail communication optional. I encourage you to fork the project and create such improvements if you need them. I'll be happy to see the project evolving and growing according to users needs. +Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. diff --git a/app/conf/schema.py b/app/conf/schema.py index 8cca55f..f35e594 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -6,9 +6,10 @@ json_schema = """ { - "$ref": "#/definitions/Stacosys", + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/Welcome", "definitions": { - "Stacosys": { + "Welcome": { "type": "object", "additionalProperties": false, "properties": { @@ -23,19 +24,15 @@ json_schema = """ }, "rss": { "$ref": "#/definitions/RSS" - }, - "rabbitmq": { - "$ref": "#/definitions/Rabbitmq" } }, "required": [ "general", "http", - "rabbitmq", "rss", "security" ], - "title": "stacosys" + "title": "Welcome" }, "General": { "type": "object", @@ -56,7 +53,7 @@ json_schema = """ "debug", "lang" ], - "title": "general" + "title": "General" }, "HTTP": { "type": "object", @@ -77,44 +74,7 @@ json_schema = """ "port", "root_url" ], - "title": "http" - }, - "Rabbitmq": { - "type": "object", - "additionalProperties": false, - "properties": { - "active": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" - }, - "vhost": { - "type": "string" - }, - "exchange": { - "type": "string" - } - }, - "required": [ - "active", - "exchange", - "host", - "password", - "port", - "username", - "vhost" - ], - "title": "rabbitmq" + "title": "HTTP" }, "RSS": { "type": "object", @@ -131,7 +91,7 @@ json_schema = """ "file", "proto" ], - "title": "rss" + "title": "RSS" }, "Security": { "type": "object", @@ -142,17 +102,13 @@ json_schema = """ }, "secret": { "type": "string" - }, - "private": { - "type": "boolean" } }, "required": [ - "private", "salt", "secret" ], - "title": "security" + "title": "Security" } } } diff --git a/app/core/__init__.py b/app/core/__init__.py index 8a6cccb..48cee40 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -5,9 +5,9 @@ import os import sys import logging from flask import Flask -from flask.ext.cors import CORS from conf import config from jsonschema import validate +from flask_apscheduler import APScheduler app = Flask(__name__) @@ -24,8 +24,6 @@ import database import processor from interface import api from interface import form -from interface import report -from interface import rmqclient # configure logging def configure_logging(level): @@ -46,26 +44,37 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) +class Config(object): + JOBS = [ + { + 'id': 'fetch_mail', + 'func': 'core.cron:fetch_mail_answers', + 'trigger': 'interval', + 'seconds': 120 + }, + { + 'id': 'submit_new_comment', + 'func': 'core.cron:submit_new_comment', + 'trigger': 'interval', + 'seconds': 60 + }, + ] + # initialize database database.setup() -# start broker client -rmqclient.start() - # start processor template_path = os.path.abspath(os.path.join(current_path, '../templates')) processor.start(template_path) -# less feature in private mode -if not config.security['private']: - # enable CORS - cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) - from app.controllers import reader - logger.debug('imported: %s ' % reader.__name__) +# cron +app.config.from_object(Config()) +scheduler = APScheduler() +scheduler.init_app(app) +scheduler.start() # tune logging level if not config.general['debug']: - logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING logger.info("Start Stacosys application") diff --git a/app/core/cron.py b/app/core/cron.py new file mode 100644 index 0000000..77f4077 --- /dev/null +++ b/app/core/cron.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import time +from core import app +from core import processor +from models.comment import Comment + +logger = logging.getLogger(__name__) + +def fetch_mail_answers(): + + logger.info('DEBUT POP MAIL') + time.sleep(80) + logger.info('FIN POP MAIL') + #data = request.get_json() + #logger.debug(data) + + #processor.enqueue({'request': 'new_mail', 'data': data}) + +def submit_new_comment(): + + for comment in Comment.select().where(Comment.notified.is_null()): + # render email body template + comment_list = ( + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.create, + "url: %s" % comment.url, + "", + "%s" % comment.message, + "", + ) + comment_text = "\n".join(comment_list) + email_body = get_template("new_comment").render(url=url, comment=comment_text) + + if clientip: + client_ips[comment.id] = clientip + + # send email + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token) + mailer.send_mail(site.admin_email, subject, email_body) + logger.debug("new comment processed ") + +def get_template(name): + return env.get_template(config.general["lang"] + "/" + name + ".tpl") diff --git a/app/core/database.py b/app/core/database.py index 7f96f5d..f6797d8 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -23,7 +23,5 @@ def provide_db(func): def setup(db): from models.site import Site from models.comment import Comment - from models.reader import Reader - from models.report import Report - db.create_tables([Site, Comment, Reader, Report], safe=True) + db.create_tables([Site, Comment], safe=True) diff --git a/app/core/mailer.py b/app/core/mailer.py new file mode 100644 index 0000000..3a1213c --- /dev/null +++ b/app/core/mailer.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + diff --git a/app/core/processor.py b/app/core/processor.py index e089f8b..b94636c 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -4,142 +4,64 @@ import os import logging import re +import PyRSS2Gen +import markdown +import json from datetime import datetime from threading import Thread from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader from models.site import Site -from models.reader import Reader -from models.report import Report from models.comment import Comment from helpers.hashing import md5 -import json from conf import config -from util import rabbit -import PyRSS2Gen -import markdown -import pika +from core import mailer + logger = logging.getLogger(__name__) queue = Queue() proc = None env = None -# store client IP in memory until classification +# keep client IP in memory until classified client_ips = {} class Processor(Thread): - def stop(self): logger.info("stop requested") self.is_running = False def run(self): - logger.info('processor thread started') + logger.info("processor thread started") self.is_running = True while self.is_running: try: msg = queue.get() - if msg['request'] == 'new_comment': - new_comment(msg['data'], msg.get('clientip', '')) - elif msg['request'] == 'new_mail': - reply_comment_email(msg['data']) - send_delete_command(msg['data']) - elif msg['request'] == 'unsubscribe': - unsubscribe_reader(msg['data']) - elif msg['request'] == 'report': - report(msg['data']) - elif msg['request'] == 'late_accept': - late_accept_comment(msg['data']) - elif msg['request'] == 'late_reject': - late_reject_comment(msg['data']) + if msg["request"] == "new_mail": + reply_comment_email(msg["data"]) + send_delete_command(msg["data"]) else: logger.info("throw unknown request " + str(msg)) except: logger.exception("processing failure") -def new_comment(data, clientip): - - logger.info('new comment received: %s' % data) - - token = data.get('token', '') - url = data.get('url', '') - author_name = data.get('author', '').strip() - author_email = data.get('email', '').strip() - author_site = data.get('site', '').strip() - message = data.get('message', '') - subscribe = data.get('subscribe', '') - - # private mode: email contains gravar md5 hash - if config.security['private']: - author_gravatar = author_email - author_email = '' - else: - author_gravatar = md5(author_email.lower()) - - # create a new comment row - site = Site.select().where(Site.token == token).get() - - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site - - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # add a row to Comment table - comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - author_gravatar=author_gravatar, - content=message, created=created, published=None) - comment.save() - - article_url = "http://" + site.url + url - - # render email body template - comment_list = ( - 'author: %s' % author_name, - 'email: %s' % author_email, - 'site: %s' % author_site, - 'date: %s' % created, - 'url: %s' % url, - '', - '%s' % message, - '' - ) - comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render( - url=article_url, comment=comment_text) - - if clientip: - client_ips[comment.id] = clientip - - # send email - subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, token) - mail(site.admin_email, subject, email_body) - - # Reader subscribes to further comments - if not config.security['private'] and subscribe and author_email: - subscribe_reader(author_email, token, url) - - logger.debug("new comment processed ") - - def reply_comment_email(data): - from_email = data['from'] - subject = data['subject'] - message = '' - for part in data['parts']: - if part['content-type'] == 'text/plain': - message = part['content'] + from_email = data["from"] + subject = data["subject"] + message = "" + for part in data["parts"]: + if part["content-type"] == "text/plain": + message = part["content"] break - m = re.search('\[(\d+)\:(\w+)\]', subject) + m = re.search("\[(\d+)\:(\w+)\]", subject) if not m: - logger.warn('ignore corrupted email. No token %s' % subject) + logger.warn("ignore corrupted email. No token %s" % subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -148,317 +70,110 @@ def reply_comment_email(data): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn('unknown comment %d' % comment_id) + logger.warn("unknown comment %d" % comment_id) return if comment.published: - logger.warn('ignore already published email. token %d' % comment_id) + logger.warn("ignore already published email. token %d" % comment_id) return if comment.site.token != token: - logger.warn('ignore corrupted email. Unknown token %d' % comment_id) + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) return if not message: - logger.warn('ignore empty email') + logger.warn("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ('NO', 'SP'): + if message[:2].upper() in ("NO", "SP"): # put a log to help fail2ban - if message[:2].upper() == 'SP': # SPAM + if message[:2].upper() == "SP": # SPAM if comment_id in client_ips: - logger.info('SPAM comment from %s: %d' % - (client_ips[comment_id], comment_id)) + logger.info( + "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + ) else: - logger.info('cannot identify SPAM source: %d' % comment_id) + logger.info("cannot identify SPAM source: %d" % comment_id) # forget client IP if comment_id in client_ips: del client_ips[comment_id] - # report event - report_rejected(comment) - - logger.info('discard comment: %d' % comment_id) + logger.info("discard comment: %d" % comment_id) comment.delete_instance() - email_body = get_template('drop_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) + email_body = get_template("drop_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) else: - # report event - report_published(comment) - # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('commit comment: %d' % comment_id) + logger.info("commit comment: %d" % comment_id) # rebuild RSS rss(token) # send approval confirmation email to admin - email_body = get_template('approve_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) - - # notify reader once comment is published - if not config.security['private']: - reader_email = get_email_metadata(message) - if reader_email: - notify_reader(from_email, reader_email, comment.site.token, - comment.site.url, comment.url) - - # notify subscribers every time a new comment is published - notify_subscribed_readers( - comment.site.token, comment.site.url, comment.url) - - -def late_reject_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_rejected(comment) - - # delete Comment row - comment.delete_instance() - - logger.info('late reject comment: %s' % id) - - -def late_accept_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_published(comment) - - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() - - logger.info('late accept comment: %s' % id) + email_body = get_template("approve_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) def get_email_metadata(message): # retrieve metadata reader email from email body sent by admin email = "" - m = re.search('email:\s(.+@.+\..+)', message) + m = re.search(r"email:\s(.+@.+\..+)", message) if m: email = m.group(1) return email -def subscribe_reader(email, token, url): - logger.info('subscribe reader %s to %s [%s]' % (email, url, token)) - recorded = Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url).count() - if recorded: - logger.debug('reader %s is already recorded' % email) - else: - site = Site.select().where(Site.token == token).get() - reader = Reader(site=site, email=email, url=url) - reader.save() - - # report event - report_subscribed(reader) - - -def unsubscribe_reader(data): - token = data.get('token', '') - url = data.get('url', '') - email = data.get('email', '') - logger.info('unsubscribe reader %s from %s (%s)' % (email, url, token)) - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url): - # report event - report_unsubscribed(reader) - - reader.delete_instance() - - -def notify_subscribed_readers(token, site_url, url): - logger.info('notify subscribers for %s (%s)' % (url, token)) - article_url = "http://" + site_url + url - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.url == url): - to_email = reader.email - logger.info('notify reader %s' % to_email) - unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( - config.http['root_url'], to_email, token, reader.url) - email_body = get_template( - 'notify_subscriber').render(article_url=article_url, - unsubscribe_url=unsubscribe_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def notify_reader(from_email, to_email, token, site_url, url): - logger.info('notify reader: email %s about URL %s' % (to_email, url)) - article_url = "http://" + site_url + url - email_body = get_template('notify_reader').render(article_url=article_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def report_rejected(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - rejected=True) - report.save() - - -def report_published(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - published=True) - report.save() - - -def report_subscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - subscribed=True) - report.save() - - -def report_unsubscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - unsubscribed=True) - report.save() - - -def report(token): - site = Site.select().where(Site.token == token).get() - - standbys = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published.is_null(True)): - standbys.append({'url': "http://" + site.url + row.url, - 'created': row.created.strftime('%d/%m/%y %H:%M'), - 'name': row.author_name, 'content': row.content, - 'id': row.id}) - - published = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.published): - published.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - rejected = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.rejected): - rejected.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - subscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - subscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - unsubscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - unsubscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - email_body = get_template('report').render(secret=config.security['secret'], - root_url=config.http[ - 'root_url'], - standbys=standbys, - published=published, - rejected=rejected, - subscribed=subscribed, - unsubscribed=unsubscribed) - subject = get_template('report_message').render(site=site.name) - - mail(site.admin_email, subject, email_body) - - # delete report table - Report.delete().execute() - - def rss(token, onstart=False): - if onstart and os.path.isfile(config.rss['file']): + if onstart and os.path.isfile(config.rss["file"]): return site = Site.select().where(Site.token == token).get() - rss_title = get_template('rss_title_message').render(site=site.name) + rss_title = get_template("rss_title_message").render(site=site.name) md = markdown.Markdown() items = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published).order_by( - -Comment.published).limit(10): - item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) - items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.rss['proto'], - row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), - pubDate=row.published - )) + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (config.rss["proto"], site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (config.rss["proto"], row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) + ) rss = PyRSS2Gen.RSS2( title=rss_title, - link='%s://%s' % (config.rss['proto'], site.url), + link="%s://%s" % (config.rss["proto"], site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), - items=items) - rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') - - -def get_rabbitmq_connection(): - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] + items=items, ) - return rabbit.Connection(parameters) - -def mail(to_email, subject, message): - - body = { - 'to': to_email, - 'subject': subject, - 'content': message - } - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.send', - body=json.dumps(body, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email for %s posted' % to_email) + rss.write_xml(open(config.rss["file"], "w"), encoding="utf-8") def send_delete_command(content): - - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.delete', - body=json.dumps(content, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email accepted. Delete request sent for %s' % content) + # TODO delete mail + pass def get_template(name): - return env.get_template(config.general['lang'] + '/' + name + '.tpl') + return env.get_template(config.general["lang"] + "/" + name + ".tpl") def enqueue(something): diff --git a/app/interface/form.py b/app/interface/form.py index 42d6f2c..e1e67ec 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -2,46 +2,70 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort, redirect +from datetime import datetime +from flask import request, abort, redirect from core import app from models.site import Site from models.comment import Comment from helpers.hashing import md5 -from core import processor logger = logging.getLogger(__name__) -@app.route("/newcomment", methods=['POST']) + +@app.route("/newcomment", methods=["POST"]) def new_form_comment(): try: data = request.form # add client IP if provided by HTTP proxy - clientip = '' - if 'X-Forwarded-For' in request.headers: - clientip = request.headers['X-Forwarded-For'] - - # log + ip = "" + if "X-Forwarded-For" in request.headers: + ip = request.headers["X-Forwarded-For"] + + # log logger.info(data) # validate token: retrieve site entity - token = data.get('token', '') + token = data.get("token", "") site = Site.select().where(Site.token == token).get() if site is None: - logger.warn('Unknown site %s' % token) + logger.warn("Unknown site %s" % token) abort(400) # honeypot for spammers - captcha = data.get('captcha', '') + captcha = data.get("captcha", "") if captcha: - logger.warn('discard spam: data %s' % data) + logger.warn("discard spam: data %s" % data) abort(400) - processor.enqueue({'request': 'new_comment', 'data': data, 'clientip': clientip}) + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").to_lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") + + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # add a row to Comment table + comment = Comment( + site=site, + url=url, + author_name=author_name, + author_site=author_site, + author_gravatar=author_gravatar, + content=message, + created=created, + notified=None, + published=None, + ip=ip, + ) + comment.save() except: logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) \ No newline at end of file + return redirect("/redirect/", code=302) diff --git a/app/interface/mail.py b/app/interface/mail.py deleted file mode 100644 index ccebbb9..0000000 --- a/app/interface/mail.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/inbox", methods=['POST']) -def new_mail(): - - try: - data = request.get_json() - logger.debug(data) - - processor.enqueue({'request': 'new_mail', 'data': data}) - - except: - logger.exception("new mail failure") - abort(400) - - return "OK" diff --git a/app/interface/reader.py b/app/interface/reader.py deleted file mode 100644 index fb6149b..0000000 --- a/app/interface/reader.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/unsubscribe", methods=['GET']) -def unsubscribe(): - - try: - data = { - 'token': request.args.get('token', ''), - 'url': request.args.get('url', ''), - 'email': request.args.get('email', '') - } - logger.debug(data) - - processor.enqueue({'request': 'unsubscribe', 'data': data}) - - except: - logger.exception("unsubscribe failure") - abort(400) - - return "OK" diff --git a/app/interface/report.py b/app/interface/report.py deleted file mode 100644 index 297b76c..0000000 --- a/app/interface/report.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from conf import config -from flask import request, jsonify, abort -from core import app -from models.site import Site -from models.comment import Comment -from helpers.hashing import md5 -from core import processor - -logger = logging.getLogger(__name__) - -@app.route("/report", methods=['GET']) -def report(): - - try: - token = request.args.get('token', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(404) - - processor.enqueue({'request': 'report', 'data': token}) - - - except: - logger.exception("report failure") - abort(500) - - return "OK" - - -@app.route("/accept", methods=['GET']) -def accept_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_accept', 'data': id}) - - except: - logger.exception("accept failure") - abort(500) - - return "PUBLISHED" - - -@app.route("/reject", methods=['GET']) -def reject_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_reject', 'data': id}) - - except: - logger.exception("reject failure") - abort(500) - - return "REJECTED" diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py deleted file mode 100644 index 51830fb..0000000 --- a/app/interface/rmqclient.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import pika -from conf import config -from threading import Thread -import logging -import json -from core import processor -from util import rabbit - -logger = logging.getLogger(__name__) - - -class MailConsumer(rabbit.Consumer): - - def process(self, channel, method, properties, body): - try: - topic = method.routing_key - data = json.loads(body) - - if topic == 'mail.message': - if "STACOSYS" in data['subject']: - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - else: - logger.info('ignore message => {}'.format(data)) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) - except: - logger.exception('cannot process message') - - -def start(): - - logger.info('start rmqclient') - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] - ) - - connection = rabbit.Connection(parameters) - c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') - c.start() diff --git a/app/models/comment.py b/app/models/comment.py index 502f0bf..10f7301 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,11 +13,12 @@ from core.database import get_db class Comment(Model): url = CharField() created = DateTimeField() + notified = DateTimeField(null=True,default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_email = CharField(default='') author_site = CharField(default='') author_gravatar = CharField(default='') + ip = CharField(default='') content = TextField() site = ForeignKeyField(Site, related_name='site') diff --git a/app/models/reader.py b/app/models/reader.py deleted file mode 100644 index 212c74b..0000000 --- a/app/models/reader.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - - -class Reader(Model): - url = CharField() - email = CharField(default='') - site = ForeignKeyField(Site, related_name='reader_site') - - class Meta: - database = get_db() diff --git a/app/models/report.py b/app/models/report.py deleted file mode 100644 index bda6e68..0000000 --- a/app/models/report.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import BooleanField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - -class Report(Model): - name = CharField() - email = CharField() - url = CharField() - published = BooleanField(default=False) - rejected = BooleanField(default=False) - subscribed = BooleanField(default=False) - unsubscribed = BooleanField(default=False) - site = ForeignKeyField(Site, related_name='report_site') - - class Meta: - database = get_db() diff --git a/app/run.py b/app/run.py index d1cfec0..a46b640 100644 --- a/app/run.py +++ b/app/run.py @@ -3,7 +3,7 @@ import logging import json -from clize import clize, run +from clize import Clize, run from jsonschema import validate from conf import config, schema @@ -15,20 +15,19 @@ def load_json(filename): return jsondoc -@clize +@Clize def stacosys_server(config_pathname): # load and validate startup config conf = load_json(config_pathname) json_schema = json.loads(schema.json_schema) - v = validate(conf, json_schema) + validate(conf, json_schema) # set configuration config.general = conf['general'] config.http = conf['http'] config.security = conf['security'] config.rss = conf['rss'] - config.rabbitmq = conf['rabbitmq'] # start application from core import app diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl deleted file mode 100644 index 701b55f..0000000 --- a/app/templates/en/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Hi, - -Your comment has been approved. It should be published in few minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl deleted file mode 100644 index 5a81758..0000000 --- a/app/templates/en/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Hi, - -A new comment has been published for an article you have subscribed to. - - {{ article_url }} - -You can unsubscribe at any time using this link: - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl deleted file mode 100644 index 0960555..0000000 --- a/app/templates/en/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NEW SUBSCRIPTIONS{% else %}NEW SUBSCRIPTION{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}CANCELLED SUBSCRIPTIONS{% else %}CANCELLED SUBSCRIPTION{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}PUBLISHED COMMENTS{% else %}PUBLISHED COMMENT{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}REJECTED COMMENTS{% else %}REJECTED COMMENT{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}STANDBY COMMENTS{% else %}STANDBY COMMENT{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/en/report_message.tpl b/app/templates/en/report_message.tpl deleted file mode 100644 index c1b5056..0000000 --- a/app/templates/en/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Status report : {{ site }} \ No newline at end of file diff --git a/app/templates/en/unsubscribe_page.tpl b/app/templates/en/unsubscribe_page.tpl deleted file mode 100644 index a52afd7..0000000 --- a/app/templates/en/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Your request has been sent. In case of issue please contact site -administrator. diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl deleted file mode 100644 index 9464cb0..0000000 --- a/app/templates/fr/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour, - -Votre commentaire a été approuvé. Il sera publié dans quelques minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl deleted file mode 100644 index 1d79f8a..0000000 --- a/app/templates/fr/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Bonjour, - -Un nouveau commentaire a été publié pour un article auquel vous êtes abonné. - - {{ article_url }} - -Vous pouvez vous désinscrire à tout moment en suivant ce lien : - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl deleted file mode 100644 index 9c8f52b..0000000 --- a/app/templates/fr/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NOUVEAUX ABONNEMENTS{% else %}NOUVEL ABONNEMENT{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}ABONNEMENTS RESILIES{% else %}ABONNEMENT RESILIE{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}COMMENTAIRES PUBLIES{% else %}COMMENTAIRE PUBLIE{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}COMMENTAIRES REJETES{% else %}COMMENTAIRE REJETE{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}COMMENTAIRES EN ATTENTE{% else %}COMMENTAIRE EN ATTENTE{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/fr/report_message.tpl b/app/templates/fr/report_message.tpl deleted file mode 100644 index 45f08e7..0000000 --- a/app/templates/fr/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Rapport d'activité : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/unsubscribe_page.tpl b/app/templates/fr/unsubscribe_page.tpl deleted file mode 100644 index 3cd63e8..0000000 --- a/app/templates/fr/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Votre requête a été envoyée. En cas de problème, contactez l'administrateur du -site. diff --git a/app/util/rabbit.py b/app/util/rabbit.py deleted file mode 100644 index 042e272..0000000 --- a/app/util/rabbit.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 - *- - -import logging -import pika -import time -from threading import Thread - -logger = logging.getLogger(__name__) - -EXCHANGE_TYPE = "topic" -CONNECT_DELAY = 3 - -class Connection: - - def __init__(self, connection_parameters): - self._connection_parameters = connection_parameters - - def open(self): - - self._connection = None - while True: - try: - self._connection = pika.BlockingConnection( - self._connection_parameters) - break - except: - time.sleep(CONNECT_DELAY) - logger.exception('rabbitmq connection failure. try again...') - return self._connection - - def close(self): - self._connection.close() - self._connection = None - - def get(self): - return self._connection - - -class Consumer(Thread): - - _connector = None - _channel = None - _queue_name = None - - def __init__(self, connector, exchange_name, routing_key): - Thread.__init__(self) - self._connector = connector - self._exchange_name = exchange_name - self._routing_key = routing_key - - def configure(self, connection): - - self._channel = None - while True: - try: - - self._channel = connection.channel() - self._channel.exchange_declare( - exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE - ) - - result = self._channel.queue_declare(exclusive=True) - self._queue_name = result.method.queue - self._channel.queue_bind( - exchange=self._exchange_name, - queue=self._queue_name, - routing_key=self._routing_key, - ) - break - except: - logger.exception('configuration failure. try again...') - time.sleep(CONNECT_DELAY) - - def run(self): - - self._connector.open() - self.configure(self._connector.get()) - self._channel.basic_consume( - self.process, queue=self._queue_name, no_ack=True) - self._channel.start_consuming() - - def process(self, channel, method, properties, body): - raise NotImplemented diff --git a/config.json b/config.json index 1681d40..5abcdfc 100755 --- a/config.json +++ b/config.json @@ -11,20 +11,10 @@ }, "security": { "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", - "secret": "Uqca5Kc8xuU6THz9", - "private": true + "secret": "Uqca5Kc8xuU6THz9" }, "rss": { "proto": "http", "file": "comments.xml" - }, - "rabbitmq": { - "active": true, - "host": "rabbit", - "port": 5672, - "username": "techuser", - "password": "tech", - "vhost": "devhub", - "exchange": "hub.topic" } } diff --git a/demo/app.js b/demo/app.js deleted file mode 100644 index aade06b..0000000 --- a/demo/app.js +++ /dev/null @@ -1,14 +0,0 @@ -var http = require('http'); -var finalhandler = require('finalhandler'); -var serveStatic = require('serve-static'); - -var serve = serveStatic("./public"), - port = 9000;; - -var server = http.createServer(function(req, res){ - var done = finalhandler(req, res) - serve(req, res, done) -}); - -server.listen(port); -console.log('serve static resources at http://localhost:%d', port); diff --git a/demo/package.json b/demo/package.json deleted file mode 100644 index 5876a71..0000000 --- a/demo/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "website", - "version": "0.1.0", - "description": "Web site", - "main": "app.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Yax", - "license": "BSD", - "dependencies": { - "finalhandler": "~0.3.3", - "serve-static": "~1.9.1" - } -} diff --git a/demo/public/css/font-awesome.css b/demo/public/css/font-awesome.css deleted file mode 100644 index c0a5993..0000000 --- a/demo/public/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/awesome/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/awesome/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/awesome/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/awesome/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/awesome/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} diff --git a/demo/public/css/grids-responsive-min.css b/demo/public/css/grids-responsive-min.css deleted file mode 100644 index 7bcfd32..0000000 --- a/demo/public/css/grids-responsive-min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} \ No newline at end of file diff --git a/demo/public/css/pure-0.css b/demo/public/css/pure-0.css deleted file mode 100644 index 14497d9..0000000 --- a/demo/public/css/pure-0.css +++ /dev/null @@ -1,11 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -/*! -normalize.css v1.1.3 | MIT License | git.io/normalize -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} \ No newline at end of file diff --git a/demo/public/css/style.css b/demo/public/css/style.css deleted file mode 100644 index 230b848..0000000 --- a/demo/public/css/style.css +++ /dev/null @@ -1,176 +0,0 @@ -@font-face{ - font-family: 'open_sansregular'; - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot'); - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot?iefix') format('eot'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg#webfont') format('svg'); - font-weight: normal; - font-style: normal; -} - -body, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"] { - font-family: "open_sansregular"; - //font-size: 14px; -} - -h1, h2, h3 { - font-size: 33.75px; -} - -a { - color: #045DB7; - text-decoration: none; -} - -a:hover, a:focus { - text-decoration: underline; -} - -i { - margin: 2px; -} - -pre { - background-color: #F5F5F5; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px 4px 4px 4px; - display: block; - font-size: 14px; - line-height: 21px; - margin: 0 0 10.5px; - padding: 10px; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; -} -#banner { - background-color: #EEEEEE; - min-height: 20px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - font-size: 20px; - font-weight: bold; -} - -#banner a { - color: #666666; -} - -#banner a:hover, a:focus { - color: #0099DD; -} - -#banner p { - display:inline; - font-weight: normal; - margin: 0 9px; -} - -.l-box { - padding: 1em; -} - -.tag-1 { - font-size : 12pt; -} - -.tag-2 { - font-size : 10pt; -} - -.tag-3 { - font-size : 8pt; -} - -.well { - background-color: #EEEEEE; - border: 1px solid #DCDCDC; - border-radius: 4px 4px 4px 4px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - margin-bottom: 20px; - min-height: 20px; - padding: 19px; -} - -.title { - font-weight: bold; -} - -#sidebar i { - margin-right: 5px; -} - -.nolist { - list-style: none outside none; - margin-left: 0; - padding-left: 15px; -} - -.label { - background-color: #CCCCCC; - color: #FFFFFF; - font-weight: bold; - display: inline-block; - padding: 2px 4px; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - vertical-align: baseline; - white-space: nowrap; - border-radius: 3px 3px 3px 3px; -} - -.comment { - clear: both; - display: block; -} - -.inline { - display:inline; -} - -#footer { - background: none repeat scroll 0 0 #EEEEEE; - border-top: 7px solid #000000; - clear: both; - color: #444444; - margin-top: 30px; - padding: 15px; -} - -.divhidden { - left: -9999px; - position: relative; -} - -.preview-markdown { - border-radius: 10px 10px 10px 10px; - -moz-border-radius: 10px 10px 10px 10px; - -webkit-border-radius: 10px 10px 10px 10px; - border: 1px dashed #000000; - background: none repeat scroll 0 0 #a0bbb6; - padding: 10px; -} - -.button-success, -.button-error, -.button-warning, -.button-secondary { - color: white; - border-radius: 4px; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} - -.button-success { - background: rgb(28, 184, 65); /* this is a green */ -} - -.button-error { - background: rgb(202, 60, 60); /* this is a maroon */ -} - -.button-warning { - background: rgb(223, 117, 20); /* this is an orange */ -} - -.button-secondary { - background: rgb(66, 184, 221); /* this is a light blue */ -} diff --git a/demo/public/fonts/awesome/FontAwesome.otf b/demo/public/fonts/awesome/FontAwesome.otf deleted file mode 100644 index 3461e3fce6a37f2321ecbe64707f04c0a4f05424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75188 zcmd42cYG5^*C@QQyeoNEmI+v3OJ1!hp?BN#Bql&0F(rUQ=*C61jEjsU_uhM#yN!)a zZ=nSOfy5~U0x2Yzbn+xmdPp$|WF(Ia_sq&BJV=l2St!J@u6Ld}hLIigG(oNmSNVYp2bu*`8&mCT(g%JrKs|W6VjPjD7@?@<57`EsF_Gij(~7S;$jX z2uy5njLz_R|~-giuu0_{g+f+ve88OQ{Hz6>Y}qGm4HH8LdoEP_Ir~wHA13xKq37E z1Q7#%ImkKEQfdVC%s|@tAvjG9rGf|G%tLS)wVE;wz~z*JdUGJ{Lb24ffiy>{sLuw{ zN=i%p4&x(nc7ggcB(4K#2{l|&I*@jvl#*QoX(=^T^1?vc`5_#d8Y8(m0B0V8%cxE# z$pBnVc{p_qq+KX?r%B0{5Tf&5n`|=c zVocso$A%h=aRy_sSr<0ddtr36w}@);HtI||V*&u5GQ^q}ChAOv7#*33dEO5J<`I%J z*XfL=NJFf*@6;JnrxS?Jv(dU>lMZNv>x|xJgST0)^ZvUTCS9nR;D;OFCALM<4|cS0 zYNX*m0;fd-nOKu<8nuWrP;pc;Wuzjh2ue?xfq7<9)2SJhFQgVyVemeL(m{GHV42{( zj*5ZUn|hjxr9$DY5z3R_VDViTHB~GZO+`ceH&s%?2xUzWj8p>r63nNdWGs|hNF|Ez z3_x2)t$`3h#RG+4z;(3FM9l*V{~4dWakP0RwGPS}p_WLIvN!Z%D)eP4^k@*r2UcJ- zU?=@*H?{x2(58Ba^p5QH?|rs+TW>_~_TISOtlp~Lj^1Ov-M!a( zzv}J1P2C=H+Z$+4xIO*0|LtdQZ@V3LJL`7U?T*{WZg=0lcKa(r`~KGyb$|2y)%O?P zpZ?_!U)FuOW{B$$#SnIg%Mkex*jk1-50MUW8X_4&x!rfW>vqTOPq#nZZoBom{jU7N z?Kihy-F|Vq<@U4NPi{ZD{owY!+jnl?x_#sJZ|;=H59|LA1MdHSc=vyM#lTJ^gTau`vLeC!n{ysFfXP_Uc`a;;df9b8Q`%5CI-q;j?f_Z z08@+f2t13LIfyd|dpIWbJ7SE2M+X6Y`2Iv zkp55o6=8%9;E*zdF@cM1gm+?lAU^<05&JWMOK=9?GfrY#nxQ=#37!98@E7s2C_kX( zC)VL8>pEDTzy|wG(u4WIx(CZZyip8U549cAFn<07M;srB30*Ni03+$ax};f-cVgcG z?mU=>^dM|7CT$V}dFOaPnef&?TC8tyti(D1AN0WcgLMzq`5)sfN=5Jn`%Q2L%ZV|e zl|>C1nDg+#cYyEwFueh|8;M1@qnrlv{tx2;EpI}L@Bj%;S(HBnvCo4r5Z3J3VAh|L z<;C|91Fq}f+8ik7{a@>YGGgBWt|0H6vm9+D_>zG%!GU*vgSez_v3`gN?0**q@gSZe z&4DsfsLMf%#B&~$%c2BmvjBC70pNldvK)OGz|)9*7$^_8{)72JI)izrZzX|2bz&g6 z5X63xu^GT)2Fno{M$)8hgZ%>hi3CQMO{9n`r5)Xz4^*h=``X=^^&>Ji>7z6GQLVJL| z_aKddx*yOSg#T~iaf59p@jw_#VuBCxViH3?=0bWXsoR0$7|#Km$Kk!l!}JNx|I;6t zQ24u_Le9kh{ZB$TOd^pe9yTF>YR?YaZFd!x{0fp_1!PlmxoWQIAlbaCRI;O$No?ms&tQPAvhI zcLfzlZKi@i0oH?3y#tirbSj%Fr|PH{s)IU29j9KRy5UrMnfj3Wg8G5#rT(J&;SlaD zkxPb1R1%Fukc^g0koZa-mn@S6N`fTOk~m4Kq)<{PX_a(J4ogl*PD{>7UX{Elxh%OM z`M2a}$#0TBB}j75iFF$8q;VSOd9(^983P8*!UoeWONP6bYtPEAf-PDhGoA5e9%B|W&ob+o%}g+} zor!0%nS7>*zlX3A#CmdZBB z^s;1GuB<{3O7?^7SJ@vjyIdjH$RCx@kk69Omp?9FB3~hY zP97?ck{jen@yA4hRgPR6M?4L6qa z zy9%44-&Nu&cXe|e?y7dxx<2YU+11B&w(ES?CtMe~E_DrXUFEvYb))N6*AUlm*X^#c zu141c*A&+b*BsXZ*AmwX*BaM)*Jjt9uAQ!XT@Sh*bv@yF+V!04E3U7*zUBHsM0Bvp z6ccZX3^nP3jiGv7Y=SP@5T^rEoY8DD2OCKU(#6D?ljFg15*`^HW7Y>n2OA@FW<5zo zn#_hMqb|mfpi40rVuB)L#mEqEHiUXs4$|w0y-7?aMuwU7@FF5MC|VazP#^`i7&DX<)1tk} zk=!0{GDn6dlQu`jh5?RPWFRTxg$IY~$cO|bM26`MaZp`6>27Q&40mu`5NR3E4I_kr z1lY79%_e=YQ3vxC8byX4tX^?JA;FeMmyr6BCv*unaJ31gP1Ud z8g*c=(i>nNNwWv07Ros0I3ySbQJn2yqtTG)7+Nvq7)~)6ALn>UCRQ8;!U(_=ktw>M z@c8KHfut!WI67LF6dD~I6a1hh5s~3$Fye)WM?yoAflXkLNgNt&FzH}OVk2WC#FoNv z#p*(Xqjj-iP#aj~3^AdGm>8J6nBa(5-GkAIj~g5t(j24CoT$^m+!*5G2&GV1MB%_g zI-{e8fi$Ugpp^+aBbWk%2OF(77$Z}N-7gw=G$<72*lbRW1eP<3ts;CvSXIn8*fa?0 zG#a9G$*{^y15GrU_3<$wrl9yZaj7{nqMEMi#cu~7sM_|IsF(ntoqpS$k<48aWzyeH0I4A^|4`iyr2#gaSZHP93;}hy z_|kz4)q@a=j}Cz)409Qw6Gu+0m+TIXoP+Vm#uOA22Bpk;1FV5Kohd#N8gJ4OZgJEY z92M4KLFx*{R0vo_m3`OVNl># zoN#lDA%yIqjz#9+RFPkH!~^q*t#PD8iqNmvSYWlm9W;7y*+TCh7EEN29uiI@t4o4* zi2HJ=fy|0Y+_z0JP>${VqCg%1PX*1KJ@+A5ZUU||!jg-OGn?W= z9EqTrbA00BQ&Py-MCxD;BMBX%6a1YbYJo`~sf!L96zz`s$#xI~k`8zwKGLMu!9EiT z3l1(EL&IRvJ2pabX^P9@VSroTK7zY941^LXFt&2BCgutEv>&3VoZp`9CqKt_l=1{Vr z9xN(RB7~3`2KgHTa>Zxn+y}6M`!5iQM1T;i2N2TwL8vJ8{*fVXgM55^A^0W3HWeSD z6FV}HPJ&q@;b9oayWr$7n19eAf!AQH2Bb)2EMy~$!7<^W)J1_#6&pTd_Kc87mPcQP11(l~fAE6f1f>c9p^hIRIw1W-IdJrf%cK%EPya{+ZO zpw0!EFOSVFUBT2u8z$zTeZ24*L-k2%VmWX>=zGf3to8zXy8wnY{q zYm)7j9gzJ?_MPmOtXFndW)n>ff_%Jun%q}D7mN*0%Gb-c%6G^Uqs7#NV;?jm*JT!y*~ckyx==`z-3xy!RI8(g-6p&#U8~0 z#W}@0aNz$&@r$At4*Pv@)R(y`T!*@ja2@YD6At)K!2y309PN#+X|7qWd9FopptrcT zxbAY@=X%)nnCnZfmt3#8e&Tx5^()u!T>o^%N@wLzrA9eMIZ^4OoTv0xE>=FRT&dio z)GOnaY06w>iLz2zt87>9Q65vCQNF6YqjLx(`o+3HtDMCLFgiZqS^nw)69*WaefT2nCrH8J29+gL^zW`P$lw>KXxEN7rc%`Ct`NVIM80kB!xH-P=yD z)8Zax-L`G(1RRL8JeQoFp3E`RSWY&T=TxX06}9sXY&*Y{7i<>$Db+YuTPm|Jz`x*M z>h$RJAD#@MLF~WGlurXZ!z_p(*eV$j=mt zic5-$)hREIwIpgYQ?rwEljVuUSe2fp#@@0tq)Klu*48G~r8K0w&34$+O-WWb*;5K zmyc}VYET<;VqP^A0a?Au?;M#b5xIO%B-YPzJ zp3bkf6xM1iwa57j7eZLh!rlu^0KO`Oz_d1}r3CWN^C-}H;D=)tv0OFV-!q1_^*qXQ zt+QfW2k{$ELD*Jd&_T1mu9Kb}K#SY+u7^ybYe@ku!Xj<;{Jmpn-u3!^h*)X9x04H;T1h5z!i7ttko zCHZ;k9gU{_O^sDGjaut-D2r1Oz9%d~Izd>3bcC?T(fz=n*j;9IcQ!g!fi}RwM^~R@ zxh7s~53~lF*`25~n{@O|zpc{Cq8q4D^!AGOUJYu7)o#!IK+xp+QSYOyn(hJ#k% zC{1ikd3mfb7d?XJZ9q?JF27Y>_lEH0vp;R?-LW@sf5Cymmg45d$~O5ewsL1iLsnzh znV=JqU&^^s6ezU@_VxI)eXrVk&_cdH$hMV!W&OmHi#2xNGrZkbw)o`4S2k#ELH*lk zbLR_uXP02jv}WC8IgLHC)E;S%RCBT&EH@7%L)Jt#lb;A<9(^yiG+L9ER+OJE1f>{4 zwyM4QkZkL|kTWUT{HErjX3fF<4IQ0=b%k|-b%lKaozS^I>!7B&xxBhbIMcfC)JtkF z+C}z~;Z#towmLnnB2A-g<2P)e^YaUHa@Dz&Io0`ED{tq~vpn!%xHX)vuCBDysyX}` ze*HBh$8!bzxD1a3!68E)Y&uLYWC4s{e-6K<;Uf5i7M`OsGxJhY&@7hgDD{$2QchLG zV+JuxSo{_K3e9Cpd3)d_-WrG=;~|H>LSF%EqdCC;ukliDco@q)$JzsDu?JZJjlp9` zso5-IF!Ll&kO{1m5;_*d5h1i9%LIR%K3Q7^XcM3YrR93gkI^j^(|iK{?0?8PHaN6YweEq^qi1 zJa)gul<)yr>?FJUEtw;Aiao@8VM=z2&*YJkSIvPAr}3Oa40GdItHtiVp5?+y@!Eii>{SLG zMJezc6*yq`#VmIbto8OD)deEn_1GWzV?C1d5cwa21ugTrzUUh*_Y`Y$$30K6+&jE& z-hLi9XdfghST2jb*OLfU>k*VhdM>k}C9hS(Enc%`F$YqY^P;o3@D+HtKV~)CW2zf> z2yb8+_PUJcYj8MDz>nwxgrv;Ogm`s-ip<=Q(4MI+sH-ok*VH#uRM)}g@B?cHuJJ%p zcEbjb8z98&yazDT4c_y@hCdw~#nBB7H7%`bZu6dysH|1cmqB| zV|P3s)iY=ss;FzRv{W~ER@7A1l-I~BnDV@eyz0EJU=P@Ut7uflaNjJt4!f|K9S8G? z+@Io!*zqp%x;1M!NRes5oFY^<%Ts`adC zpvzO4?2?QOwS6&2KgJp<{9*f;)@SJgXXsOZ2$rke5lGUr1z<|lHjQil? z#Sb`D$5pzmdS_Fs#(IZot4mML7ZL*K>RDvQus*U(b26Inu8 zkILQIT?x%{PSuvylmblaKcC;jr!=Ltp#Yx4!?AKQ9vUr-vd^Xs3?4FW93BFT8Z88! z36@_sGlln}{Xyw#s4FY46{;)CDm6v2tek>^-0a*;PdjbjL(i~&A>-zOaKOH;JV%te zb=yvN6K#Qr1T9w@%5wdI;lR7u&{Xs@h$u9N#_!4Ofv8z!ACDGV#ef&mTm>kV-G8vk zJ*l1MI<2YBP>`I&$@Z>#y5VUJmf(o>u#`rki1i3=cu~YFXr#bZL(QBj=GT}B6X55- zgag0+dVuC;vlliz2_1X^C&Tjs2ZC<~?`6xH#8;3VsefbtM%X*@i}F0V6&E(V{q}_m zZv#`i%a#H^xJ;gmUEud;;)oi?q+eAq@+n4o!f)DN71lwL7u^*B)WpnmFXa z|Gl<2*=pcoG6C8nZ1-vOG)Rk+cm0`jXbg=CnR9py?LQKoVz`>xT1&OX(@IXqNSij%Ro&t8ev`R=44NUJZ7N_Dl`Tb z4g<|u!E?b{V`5@LvhmI|kNU=jrp9`C$8JuwHZbg&$y+X8zj5Zx4?cg-cdpj{7xyTu zEUqc4$*;+4%xlhR!U9V2!27w)c>bbQ%NM65r=(RG<)x*iB_Mh9?K!)5Yq`dZ_R_{i zPp+QSO4j6()fYcF!hdja^%9soH{SXb60RZsb>uDeqMzhozlS|oh8M##2DW?-#tT*= zx{FTYllU&D`V!6?M0vH6aYUndSdbtDGWnJKeirND)Buu$`(X9OC0usf&cb$0`_9U` zHUYMOGlxp?7PJK|g)vxyM#HlNEx{1uC0q-ugo-SjMSH5EYGp`)Jh)&>QK=fFtV#6_ z&pqq^n$VD9Nv=Dc3fpyGQUu1z`cK?d=fpZhtR%mkFr~UO+xuxb*Fj>c}QeCY}qql(e)IuAOB>Mu%3D zhm(N{f0e`cf%$VlWJ@__G1~+)NVm1s?cS}1xU2=FGVr7)Y$`v&;iR=CJ-JNaa&wCE z^I=D8ZGm~{W;kQEDH%9?93Bf}SqtO1l+^)i4AcXS!9mCaKaE1L3#avR?gkNv+@Uep zGQjSH-vHeLjRK*{aCXobI4i7v4%}BL3VyvQaCV_`DXN45j~A+mqV`CrgOsH3zEj}` zl7A8FjTF3sDyeSCI>~y;M#*N$R>?L=h$KOhBzal#yOWbsyVD`3Po-0&I_YldtIl%g z)y`GUztU6b)%3^A2-zyxr}AKMyC`%S?~?0s&gCAvk=@A+8INpxB=(UTLj#5lA9mM0RrTa>kKqf4hYYV6-ZA{ja6Dqli0vcJdCc`l z_NefrJuj-uHIp z_ag5-Bk7SbBd?8`JgRzB$EXvdxzVqT2_JLe(TK6hDdQne>}%&PT|}sb7SXr&V6^@?0L85$Ikz4LBeCnk3apyM*md{H!eK3@ZOUFPv$@M z`O?6p|`6^)U^1oJ;tz5tI=SWf&zn@w=LQBeek&8^x!KY<3rYj{2rPcrU^5J_3O6i3Uo(wU+Qk_ z?uWaEPYe$V*N5*2KO3Qq*bs3%;#|bL5&zaF>a+E~MV^VYZEx7#w7q5fPf@d@)zBK2UJENyPuv9$Z?JJR>1f1mMaMttUy z%vZCrvLDIbl`|rDd>)gxIM0%IG4F2v)cnN!iu?lw`oc-Y5v9kfL(3p?(2J)J??u>?_IvPZtsu#zSw{EfZsvg!5fFx93FYN_Q>)V+>dQI z_U-Yk<|)<*&W> z`s6ocZz|t9`_>!Zr0qi+^oOZc(eIt>&=}vJ8pJ%D82e_D7_J=EEd>ru2@r4 zcqf{QAY4A$@tOXzX8>~2UtZ9$Qtyd(;wdz`BqJxYGd#W|1I#Hak1j~iuk;MSPWqJ{ z3oh$D(M~)SFsH86CoDL+((@{II=b?+gv&?a1)4(ROK21eGyrSLW&LLzM=yI`MNUUA zCw#W@sHd_Fj@q_UAeimGR-z)}z*tDyeL*GO!%tzYkgOGLBUX4Xo69TvGeHmQk=g>$ zVb!kWwsWjZ{5ojau39(DR^6AYjL`AcUv0 zLt*N?Rm2>bol{bjqwVj(7CJP;5FV_)x9Kh3wm}AH*|h!0TfEY{6f8g>@1;4Q$j;=C z@PhI~ww*m>0Xv6(0&BC#I!vAMyAweLaDsEbGZL^|zWk#fAOBNR22OZ?{`kq2j|3zj zIU0kUL0@p%-QKyYqf6dzk@dacJZmnNKC@nn)uXU_B=#IB-?nbjv?ZDnri58C>4(!` zCiT3F)$o5uKI6kRzad7w5O(b){PFxCBiNIUsY*aw_h-myPPo^D+*oBO-{+3qRll(X zyz&M#w9i#_AgaMsskN8%;CSmMdS_0`F03Ys+g8@l%Vo4o-;y~Da+ zb=^MK?us_qm3UK+buNAjU9ir38?CkuMXT*Yt@BpUXvd}h3C+&E|7OWlq7f>Tunzr> z&uXLb2rS_B@Qgr0G+NFgY9jIxZ1))CgQ#E7ECKiQJ(9pH(S(0NKaf9bbq`p__gVTa zXW4s}9QG_|XqvqW>gR!J|q1 zy}&`!_WLZHJAmj@?0=V^IqwEZG!$(gEPoC;p2zuz3F33)LFXP2 z6!Z8Tkamz23*mE)3J7t+{Y5a$$m0H0p9M{1$ti|$Tnk_`Hp|4@0R$;#2djl!0ytxv zWj>&y9h%AK!&-T;e8kfB9>;ZpwFfWIlq3*HaUJCj|Ic1 z85gS2WZnu|D*~-m+YgMRg7XVGMnFbJj^9Ukt+JyODZtn(MV4Etw)7^@5d*;>3x<3g zc!@_n(LC@%v1wTQmq&FjDHw@0c@%cm!r<9%&pGT z9^3lD)+3uzV=`j0qwq?DM_N&4GTajwWpF#tTBJn*adnYR+gqbNBR7W!MQ)K7JYwKaYx&9M!0>Za`1YU=eg+-dlTTiLVOUp~jwf5`QSo?K*EL~huT&%X6 z!A`WAM;5!g70gA*WId~@Z!GS}sK*BMjE8k{1dE@S;Ucsx_~_EoV7+I5sQp$(R&hdo zhUeQ0tMY3LYfC&WCAAebRkmf;8y>c0_8W9TbxmG>ax&@RE5+`)*l}sZz_*GSf7;h)GLpXekpa zWnHKTJ;9@u=t;f{_t1qhTd6%lbu#N@+6!8{U*CRucXr&aI1T;*5Snoyom`fY4!kyx zNzQ0$D-)VgAQZo7!Ip7qA9-(RN)GG5Snr@u#py zb!MAec4)B08pwbd&N2fG?qI`1Gg&L#e|VsoycI4)cD7aR%kJ`2qDT3@JJvf#S#$>m z{m%dG9`Z-PvGz>nNwGg{<5g7+MU6RixCixkfUUbCSCgAtl%FTy>-ajIpI4ZhqkfEq z3lV&$nn!m~pscZ>wxLSEe^@&v^RUcofk`LBpaEx1Mi5Ga5~brEWN0+R7< zumN|#!M`)JBe7m(o zh)@Y+E zW?FVmdV!Dnu%GLhH$f<6S4@X!4I}?hoiG4OH;RMxUPr(&!N2CAdu+13M6MhyB@I3R};e z(IDiVM>&3SplzxGFt9ie{?~x(*zMlWCgxT){?LgQH0;Q;j z>^Fh3ORRPeP@e>vdK3y|Pzm0OwnF7jR8^rr} za^G;GbN8vU;(fz&Y!H477S;?+bZk|VL3juBDz{f53&S&IZ2%rB?8t~sH>Jvx(lb*` zYPkLIV;kx!s_V5kkQU9q4*FK?H`p63*T9TE^7>^&3;UZpn~tf;jva`4>Y%OA`gYc) zRavLmA3?v{pNHoW{JVV`mb8yNIbPeo5v|xxW6A#C<{?fKC0>eHd+OML`FxaY(wudV z1qn+V=%lx^ufC^7ucKE{EBXnZS8*$TeOY!u(hBX;O#0KHIo*>qhh%#8kPLCZeSswV z1+?OD`+550Z|!%HM6JA+S!%OL*Ro3ceb6D?AqCc!Q>x!r{yOEDwlX)TELWqCHADzc zAbAINg-71;ggRos;b^U;tg=@7)A7r{{0_Z>vNOQzeRpP8vi!G27s1e|RAc38EE~T_Yf3g|#-+>6nW;$z_4Dit;zf!UK|v^wg4Bf8 zB^-bb;-knFh{ZOicBKnVxh)0Fx%<-grR+(P!^z6^7rYj4Q{!zDA%?7WN0K4Mkd~I4 zT9BSAH>Ibg#;W~T3%R7xg8Ji%fFQa=_EOk8_Oc^jn^J+nZw$}$xjQ=&u%oyS!0uV@ z4O3Z%f$ls^25NMz3ee#`&+0zhk?3~^V^=?LybX-I^dKc-@d5(aof)P8tFMg^CK(T? z#tz8LFfU>QXNK7kz#-8O>iQrV8>mKuY0$K;96WU8AR2n`=eK`8cnF@KuKaX`{2jdV z^G}eyGUCc`YgDN<(yCV{z&(+8!Mq(Vm^<1_cWJJaslwQ-?9!}Fe1oO5Uh`8hTVE%d zJA>H<3mVBMw(z^!OWQSQ=)jvRf5*eJs6k|YScFsO&e9IBlLqsh0|iJ)fD8?GBz}T7 zBFgFrJ}SC2dJ{j4k{&u(dQfvEf_JoGn1|ibj1rYKvedfW8u@pdBkkMOB4y=9a-`V^ zHx|QxVjsF%2etXbUqaWvBEYr!R=U&FL@c4{+s$UJeY<@-tv|BI^s45_;idSmzSF9-_Jk(0r7XELF+T}+-!phbWNk|b*W8=N1U}VpF;U3fRc5JekT+Ob zDjGF58-s@JIfAC16VTp1ZPU5@b30z}JZcQ?*sSRT#F~8$@FpElLyvm`hCG9aw11Y4lhR|T`S9u$~&jgP&yL}Rxw zOCEdfX``?nlpJ`I#|!t|W#Dw8g_Exg&YrSVR@)zlKx6yc3kmKJz{3e9vz;Abi|o^> zhGl(-AH9sHy^1HxOOldGk~HJTSCowx?63BZR$Y0!qW+3d-qc>*1ZD)6L?qXgS;{OH zb$jW~lAYQ$wRl{`C+g$9sCmytt#w^7-4X)LzYw7gUzZn`uf-z_bkUBI9Xr&OAnQle z!_P1@EENsYSjuWG>YYXKXg9Y_tKPmRy*~XwUdx8t`oFgRDSrYjehMx1hx?6StJwpt ziQ$p2WBeI>xL=R6?e<_yJ=#7c*0Xp4O-`XE^no=LeT-|-cf-1^uQsbp7cFSjwL4{Z z@2czeoi06~L4HV`izogl99~!Z%nCJJ^Nyd6lQwN?+!>@zw`Hh?b(D0LlBc_@yIb+- zuC@F`G-y6rxD3v8sd(IK91;;;RvjVOkKc1u)u5-)(=BM}H{axcqd{lU zyNChzwQ&3xjKP}IaD2Inyxv1g)u<7dBMK*Ihjnb-QnpotvvE#JAWlBeO?pK&cr~SB+Id(wIxx@72B@<7t#t%*V0!A~Tle>9fMkv8$q= z$_+@Ar_7?s8I`kopn6*aav$NvTmy+bzt8eHbEawE(Kun)S9mCz6?ZhKG!6`pL-8zH zAJ=3!qxt-vWj+&>ZqV-)5J30p&y?;3(<0!~NB5@h3(|Pmvb~(|qeof!rOHyY`Q^j% zj{SeN9@Dg<5fy1oLPL7tqGUC8k>Rb?L|IWUufSdL8#7sSqzD};K}WPDFIAqaIwPOIQ2U*kg=qp}twgXjTcDt$kirf+jmE^=yLeZ&V&~ z)6~6OR#Yq0le1<{S@G^)+2b!j?HS1l>G5Tr7TktA@^Kqf@8#U!g*{bUTD+5mdlsgx z<*zMz?1{OAIBi;HR!X8eu|B0aQ=70CRA=s}=4|R;Z9<=gNlx`XdlY#hu4AvZxuL$b zUEQA5n%1E0h{FKXYm`B(-Tez<_n8PrA2=UcAkzn#P_lY1J z2%goV+9G1~1IPxBUO`|)a0WlBK~j#~AgzIS4~Rj24kFAv!Tm4rQ5MaUS;yJO(GK4cC7KHMOVIdi?tOtTvV9i2 zyUOkh25pPof_;%M?t!B%4$S0bw?NYn6>=~b%dUV>j{=pv)XUZ_+X2=>|3RxDC`VFs zf4DbMQ2t`R{~l%HOxn8G_NgkrIKQAkYd>K>K^GK2Mw5|aX{;`(1%9`bS0<~62e={A_em3r9&C6%^p6L**l&zWG z^Lovd%W7f?fReXVuyfq&0Sh%K!6SJ>bUKX9Fn_DX>aOZy*Sz_mc39T#+OCFnZDV`) z`yKDe>np%ZtTh?c!~BVU*BkTG`p>oNKgZiq*F4lIKb;xU z5v7R=P1u$p%)^~v`|nymUp_2r!~EzeW5qIX(LDxO?iL(A-KBjBbVs%J)}%KW*qq?R zg(uVK6Kk)^l2ekFlY{@je|Y5Nq?P1Yax7)d77O|V{o!G;G?!U&ghv@y?gpFTgEF zMSWw7TJ)>|PX*+~Dn{X2e;#Gq3RNG$TDH1-G-RgMq-b(;ii>gub9SnEhZ+Zh%F^#W z8(+p9cr&OJ@io~(ZgpKj-GC!NW3{EJxG5T?bfJk~{3!qSQ{??3iqin62QoWa%$?cV zqFM|1`n0x?dv#FO9sM8|;vhQ)yT^e0s_dYCSzLj(3qA~RFqVJ zo7Zvv{CUT)dj7X@MX9!zRfi6hUWhsbR~t?qQHM5^Mn&~&`ZXT71+_S`p4bUHMMafv zI27e+ebWBqp{NU`hYoq#Ubnpt`xTrzEU$oEw7Q#lxUL6BEQ{#WiY?d@0w-@fJaCw3 zg=3lB{S{msXAIa02*9zBq^DgNWX3Y_}X$6ErwB!A=AQbiMVi>R@Z> zzUWr3zH7DwkLXronjTzM{bl;yX$M=iVB{rNNB-dKX#uaIKJY5CS|;-M+(A|S2W%zS ziA2oftT#ASFw&Q>O!W@izwr-fmu=%myeILuqaFTs{Ttt4JHTdRc?c^BaNl72Gsyso z;3RV0^aoh`(}A9ow8{FK%4oDC9@T&?;;5y4w^1Mz4?y3ty|All8ju5jHfZ7u9$O|6 ztr|`3>j1U7*30=i@F)hEsA4CZUhs$*9OjSx=3OL2gm;pY!kc91|rXf=R zhmEH(JOE53z!U8!0)~T=g!s|w3yG!ge3(e z2pYgzYQSftl>on+K{sU9q@<`*@`yVQsfOSYU}}omIszp?2VP}ur;$HzKWRTX(6Cgo z4RB#|?}57?sqp*nK4nn?POwh4PWqJnp3tra+NcW8iekaI2v=DH-xUO|g*}yp3kWb9 zS!{6Xm^^y*6Wg?#<6<|O)upnN>j?FEIQqa9#rehTZ$zRVM*MMts^}2XwdlLUH2A1M__Zl$mYddN3?i97W+GzK6O`S(EEkT9p=LKJ{#%xO86>;pG_85*;V_)MS1V*oOd zMG^-6fF`qeG_w&!fj<8jjyf+#qk-gT8IIZr_OHh_;wU*D4Y;G3^Ee7H9z#(Z0Rr?! z%TUyL1f|ZSC@8fO%&UDjty{q37tEdDf0Fb1|;<4ZWj6@J7 zzQ70|f0DQGU>;IXibhc=01WZOYFlfIfaUS%ZL&D_;e8%>92y5}@zF=g(l`fKZm=}0 z^Q`k|G;)rk2<>efp~A1j5vUk$47x#S-@NtavkL|0!)>`%UO)BeS_!smm`6dRwjrrC7nEi2ZasL%u+~Jbzajo(yNJS~! zrM5fcs@&;pwcdr_9K6ALpyYx1xtrbB-Paj zZB=@Adoja7fCpILCLbjXRMJQ^tPIewpxFYU5apm?g6|T#It945^!olQ0uLt}$fF`*XA$VjK z*bklIE1nN9VZ4rkZ&&>2#`>qir=!4UGb-MX4wW zjYofJV5b7TeWU8DaNuNmcHHLmfm*P)d)SwKhJ%;N@v$km8Ykip)%ps3b)>d3anHJ( z)p+B)(dvYnd`pUU%if*QSN?qoZM&d3d24y<%R=<`zop*Npvh>`$@h^)0PC$5ZAM$K zqO0=8t2OUjRf}#t*jMFn#uhKxLGRcAduiVt75W}$!IuR4(J*aUNl_VnaOKgdOK`0{ zFw#CujlV}(coEulV!LPvHG{IliI6 zH-Ol31OVY@G!6|1+raeC<)KRx9$TQsrDzKlKb}LSpCx=63M*>*eE4u>=+AhZygv-i z96RFUcVK496wG|f+P;ND+U{Jq!P27Ff%bPO7$Xfv_&Xd7(r&qsnUHG+x93{8$!YDOc!|K7UM*WlVnK3j{fTJH_E zWGa?@%cA>et*So1F1s4swWnN6)?{YqXXgvgO{D9eU0J<~TpzuSgYcVpqmYr9XEdpe zHKxWyE%^E`L$mQjw9ZGKof7$6syeeeyDndw+41x0Ht@2quB#T#e~Tilu1IHfBe5sk zQB{?zkJ;C%_7&|b*r&yZ?Iko?_%4!xI<-=54Fq?bJ$W4&yR^h4^$2>8+X-o>`?>j!Vc|6*06ZV zmYhDYhMUM5*08XG@fumNl~rYwq-75*;<`p5#kz&AuBfZ2B8#|sU=in&MO;vko3DNW9Q*0i`iMtZY+fm}gk$i>8+}p)B*#i(Mx=h0YUYFUOydlWPVq zIt9f4HuzTsT*bZtlO|zb?Ca4#1^HxGWESS;d6L_Kyu6~UvK-Iy?CKm#fwc-B^1u!7 ztm~%Bii=8%G+=iSCQXDZ*Brnq1Am6v0#Jljd%#soRdsG{VO@!5St;C&gKa^$bqhS8 z;maM*b!Z)Gfaj3CiWIOEdzMtdeZ8ipro6HWe4=jE)mAkYHF#oQ+j1~C`rW#975N5S z^&C9&xgWZE2#$Wf00kEmF%4%BDKQGD&~WzZaHL}Uec?}kpZBGeA^0s2f*xr zVGZmzgH;%;7B(?<lmf$OnUFs(7Jj$JFq)Y|3K zvFC$IJ_Rw*f$EV|=z4Sa#rX#!%5pGQsnB0`_(a8?3(up( zy&gToYj5*arjFEIAtr> z%U%U#E665AMXaERiq{1du8LI8Bq!y5&q=HI{=Wb6eMXvX=H$%GnRDLdd7k&U@ErHR z<#oZ03PxHu7@ie^V?LJ<{NJC<(NupIDt;XXK_OYI0JowwrXEg7Qnx3Qqy+b3U40Ck zkul7hVIBoOy-04vvNc1K`Xt0F2}BKq6TSWO}Kw)Zhk`O$Zj1WF!?PsuPp4 z(%_|Z_=80Ty)iWvR70Zmhl^o9#~3#p;C$L;IF&;9A{o^R;bIuq_4Y%XH~`8(GR(GR z!Gvc~01=`EK-UOphk|X0;AoMaC#==9;)={lbtNo(cromEi`@27pleu$0dYh!QX|)U zgZn1S7+OC^!}-{2GE^g91CSQWVK2gFbH&FD=Fg`Cg+U(CkTqiRYFE1`Eo$Y&ZRjuN zo*A+S&xwnwN`N(}&8*L@;e5MDACY`=OF>;zv#J|y7*2X?y26uWJ)8mN0nPzd1=N)# zFoc!}Hx);hx;JwVI>aNWY|-@jJpI-mCQ3<41)=7RlluLf51*`dXY2QV|{?;T}Rz4z>b_e+mU*5S%UbssBq4su&ktW8q0jd?h_!Vxa|5u>lfr|!r z(=-1!(FGhDqOJkz=SkTEypfdSa9d@6#v2fa#DoEK7?1)kI5^aWsD(?6;At>kKj?mq zJAt~odOl~{pdlB?F+&uWWPimbVTIv@0IK-kLCIE(Cf{(yjy0P%t&5W8XIe5dRdf&C zg5lDaJ_;=G`JtSttlV6#{`tGT?|#g`V4Zj|`b(+F<1u;El_fTN1wW*6;;nlsz=$kd zb83rWpIN%#U_^U|>Y)2T#UAcHY@`=nBCljhb4$(TWhx^5@kb)Pa3*HmLGGCAXhmyX z%JQviSMPkwaoByZT#DH7;xL3&!VqsnSR!2E(;`mWPbRbqofPmsJjRO2a`Jc5i@f?( zdrn?6N7qZJ(kvRXf&$&KLKuh4Ld0A!NW)CML7Mq8w`Lv) zxjNc+mga%We^o((*UMTl0SH$WIDAk<>6TI%n-==wD**u_1b(%6=~YVFNwCO!g>qsl z0|tY*6jv9VoeD&7FL9cNXCwYe1m49Afe0I13-46h+uPxDDHwY-YKOCUCs(}Fxg$!& z-uD~%b%2$-t0_C9fy-#fZrY_{EZyI~9@&rc-tN*WcF|^1w&?(efOkQbTKB>Wx}86c zt&Zoh?7?yoeb(8AJ}6ZriVh)z>2`Xv=dm5{hL9+tB*AT;b#mSjT_bv^UhlTIzZ-yN z@-&)6-?<}-ufF+wgSz(i?OH&m1~DkwlfGaEDD=AgF2$=0=1+Zf{@JTNq$i&%=s%wz z+{>6ypzry|C`t^TG$0S3tIcf6XiRSmX*#rf|E@#Q0tcXcswN7+?an&Ro`X3Bp#hT- zphMa4*y0%X&cYBKu;wKuqyW`)jyNsDke17*FE?t9%c3)53^D0c@}06EuwxWpm5)Uy z@Q9Sd3jo)&Act$-xO-F6`t%rmOx8~NLaY+u+|+axoh?Z#N-r^Sx_lIog*C)-K*1r);4HKv-cDrS)@9V|8?r)b0i92?mgq~=WRAF`xTvIn z_Z+vMv7c-#tS_qf5XmrQZeYh<09N;Dtm5rdS7dUt(c19_oN z>9H1-dQ`|7|Ed^`NK$^#Xyyw5I`2{-2>&5T@`n(ajLU(g9}M`zG7TB^<9$RZMzgqk zaNuzPp2-HhdW{4RCBY*|u>3c9CvFz)6=qIv+A(aUnX8U1SsAI4|MroF_8I>M6>;=2 zj8H1xcaT2JS1x*C|3Y=7H@I1RgFH$mojc0c@2T6pRk%Tl7LolGCWB^fe` zKB6L>zsl%T@$UC3E_|f=Aotw1i`?Di&mQQnj)+WyW_F%oKV%~)#cQ1 z&Bw~mp8L;np2u-+#Bn}Gf~Ws=sDk_+67U^YWd07t^f_~9aI{lQAGtG%%8(h)1lyiFQZTMhN!XIcc^LW zf6h*$_|VP|hwa|5clEwC2iKpUxs4A0Wsnq6h_t=x`@bBxdz{~Mq2R*%s*jQ`Y=4g< zQ^hz19Fc(>%JF@={%!GgU`h!U-BMU2i!>sOB)|*sO;*6tnN26=<#GN`{qOoe6V8fE zRyvzZB(qdxFIjLNcIO~6o{pS-imQt{VX50CeeV<*4^wpzLj7dHJh~Sva$lq7XIa2x zMf&TG;=l%D3{rFe`ZK8tEu<>;%8sUg*fbkK^^WcYTK5OMFI;GiVL4U<+*djN8S&51 z8*lq4yfaBk*Gx&NFHG^;vPEM#i&-*Z+3EK`Jay{EjpIwE=kvlB55g3JMDpX-OE1mZ zxMm$x8dA;i2`aymrdT94?<0AaEh>^Sz$8Al`^**cICEje18D^Be@a3OzT_|skZ%Yh z_7-%ASh6Xe6SULVEDxZcH=M%weThC!UZhG+a7p%RfWyX66$dDtz+v-#*sx0aI6R`4 zK;jSvim&^VL@73pzD$j^%~Iy^lv%lxLV`$`5U#dHZZqs4Sw9lI_ zaK1~VNihfs=1{t99rxz#W+1_a0S7jO5od#e2BfV_vcca0%?8>E)=sO8E+ENt6Sp8z zQs2CQW)VE1=Dn6@wZ^8z3Z}QB^kgJEQq(EQfef|Quf2g)*sm22MF4)VM$qD_Hq@5Y6(Rfy zZ}A@_xF56U-X_5>VJ*l*3dy@%fr!d-1Qp7La#1hQpzPs@gZJJ;#miR4EmzBDY)`3e z;*Zq4`L;?v@Mg@7n<|p?5osZBkmtGcN3J!0*eKom^>_PzP|NeMK8RM3M-Lrx9a0zA zE#?AVZg-{RCnUaMcw?1nkvqC_1y{D-MgP7M^P*MJt|uY7bDTUcqdBu=|IwnikE-sk zyGg~|hK+`eAhnB*(MQ#CJMI1SrghKo^1SieMs1qDL`wB^dN}=n9#D;`A3t_g+}f2Y z`HbhT4gZ-tactKyA|gRiM6A9~{_fiMGx%UwXH&?1@^yE3iX2xsEnsqU+11Ny5{ecy z&)>7KMOwBqa`(15!|7{_)X`h&OJaFC^H<ki{B0JlXt={}7idVfSth)a$!Eer1An_0|O-fal+u?w}y}MkMj=}g$CX5 zxg-GIyrIcrte!iHof%6FvD|Ch=@Toasb|b-tewr%9iP)ok~xm-km}xxzW9Oa$}#f9 z`@6ZKx|78XYP-W?vhx)=pU?qiJEZQ!8l7%6eRAdoRYFBfQ4;qFRet{>QBIK>D(aJ} z4qYZs9#PlU#_22hrW7(@iX%yCHCl3vm^-``VVbZbq2hsXgtb1uVt&l)Ni(xQIDGO_ z-L=`9UX6Y^O?qkG=tD!*AnZL&pP|!YY%z9RiRu!Xg^@dgES2t9k)oZeqCsZ|{&POJ z*t)EAO`X(iF_~3#80k&>l7ZW~?C7Z6D0O^-)t11w!i9KrD0wPR8custVBLnOqhs=I zJNXwtX`8s1%u);Alg|XbFsZ~+VlLq?y?y5Fk#;FkwaE9M@9kR{D(X}eL=P>bz0{Cp zc>y?V2T!ADAw5nCyDa3q*Fv0gA({$uCjR zeprnT43vVwiI#FS%wgndu>V&DEg>tZgNUfJNlI!Z#Mwj~2;SmTX`iq>S^MXoBZYyh zEx&R`h6rb?0&gsxTq{-^vHaI==`t)leGo@O ztd&LE5bCY0a5m=Dhh*0$RK+;xykknlSe*6QjfoW@NtMygm~1*{v2q2OmlN0^WT$gy zc2XjUV!RTua=v?$keHl=gq)Zabe@vVX^U}2W0R1?q-^ZoM&~H$ycKMN?3j=od{>zS zZWmA=_`^1F#Dq9wsuJpR$h;NG#dJ<~V3UxF#K!D;Cz;cxJVxg^Vk#3FvP0@~8l4rD zi0^Ysqyaclnx`_WQtfc&=R5d%XH}J>{ur63By$#{``9pq9a?t%VloFJOvmaSRaMUV z5J%w9qzrS!OeSwQ2?kAKD2&?^xF&a*2L`|36^;BZEWr-Nw0ZNUar7&3w<`um>~9lS z(ja7(=p-hx(ayKNeems;HcxeNO>vF1)_sylo7A_iMGnER7&7y9mC*64 z%r6s{wmRQBqC#$2@lQw@$~&oQ4V^S}KEHis#JbJvq+6q=k6h8?Sjg}G>3CiGc~jg) ze&hY$w~-+A*RtvZp2M~`rDTE_LW98Yk}!DrIGz(k4qtLQQLOJiFm`})8!k2G5V;$o zP!lmU(0cEej}sX$-&dJmX{+GY*6Pl_t*Ud>6<2fe4oE_tWAjve>3dFQWu zc=5sqywD+*+b#{cOoCN+t~K9kJV8aeVy;+MtSgCYS$uF+>niEX?N^8Wu5N}2PF(Gc z?Cb@?DDn^BYOI+4bxRb#xizA7`#aJf=C2`>s7lQM(&2Q%YUi5Lbv(`uhJ*~}1qUXH zl(M735!%*z)>m(m%J-R&O-xhGs-AUt87Fs}jUu|xOjP|D=j7qe<^%V5xvl2n&j&84 zVP~^Q%{8p6KyrKS3V8=ES1|HZuc#us;6MI8?CQ(y9D00kaZ9`>t~jn(s&iB6$tKmU z=4*F2`B4&ldB`OMMkOCyxcDI_|MbfFdDD3Mm881voswqtJBJe^nvqz-8B@zU-ae~a zTg%%jDy@}j^4Qyxs0jX+T>)XO-mq2Yo47;E&}Vg8s@asrZ>nDJS!=_ElaMeRpp=H; zU?0IjbDUo$DP6b5xmrypga<(5Dl%!!5A*qVw&R>_mdZbEiTZl_3Khcs=-*HvB-lX$ zp?2xO&ovjhgZKCRPJ&c8=)kC@y_dgCWt_YtF)`nk#IMyY(5+I(#yX1Q`H0B%i7VAx zSC>}pWQQdGl#TG|b1#0t;do!4IfMqQheTex)ks7qYK!^W;@aYxlC~BM?N(_=`*&A= zS2Y8q5h<1*MOrWz0}5RVM%bfUSrK|M@($!T<)0E)7f!;%k)50Fad8(n{d`GuagMo!-v#bMY-tlTagI-gEjLscp|;v2x@$G`cGqwEknE0iNRLzt<;FwmtW zj9s4zl0yY=m+Y;?X)%?QnoHE}?bU}*@OEU%7Mkx96?vVxD+Rt10w^Pv<6bhqS&Tf; zd|MV8sy4i!X}4moZSyi{BsC0Kth@?Xn&yP7gkZ?IW1Q@-`xsbz_TMXte+o zH4(yqlhFhPj~dMqv8Edc6hH^fC-X&qGsHu&FU1?y{HHQTx&Hn|2GT(fNWs$#=%WZU z@+Z`&8-LWjw`74#FiR-w0UH00#uw}6_usb%TKlstyE#&Z#^h*>^#=zAAtH8U?1zC7 z>OKs~0*&7+0d;78;4lNdu;)jD=68&|+H&KI%$j$a8eGS!3QxsX5vfaltqrOEMt_CE z?L;mh*PZ9eP0y4(n51gYQ8Gdm|@Az%+k9PQwws^bxuQ? zJ0mVxUuwv;>kT;tIr+%VS@VkxHnY@d%FQ*Z?dkfoOh|y~vhqwx$r&-t+0uN=R&zdY z&9HBY_ZaLsO?mFTVzVWyI6c+m);pzoM+zLiRK4DoSDu?ym|@N{8h`_Kq!;Z-PIXH2 z3vx|1J})ENnUgDp|7XZCdScRI^P{B+o-LJn&Sl(faut*~Om2Iz-Q#eS=D%g#y2Y53 zha{&dLu#_y^X=*ao86{z7^?Cd#mdX2FVAEsPIhD#n{3go4r^M@j-qsDygP5#&fKItV|IL@G|iq-l;%uv?QrE( z<&;?~GfSmKIZ0LN>SEn)TOP0Xtlw8yWwpf>=H?d|^7ZyiJMx?kgFC4-&J>rPTTq!> znC(fl7)zv9FnsZ^Jf@_eIL&RTHk4#~QXT0oqiyrs9$SLZ@Q$rmYB5>zRRzV_@0I22 z&3TPByS1P+HQ$85Z6Wls3iZxxN1k$e}FLJo71r>G& zsLUD8JfwFw*eYwCh4s0GMydR($?lwDeX7A}Zpk#JOC8XqD^$D8*7yQrNle{|0#lN6 zFHj`TJa=q5e)Rfew;?|+AwMlYr7}HLiV>V?$OUjXEiExW+3GaM=}ftdlxxZ|8LWDv zq1^7;mTj)Km0PN^+1@3_Y7DOeM`fPFRGgEa4z0{oPfU5C&015G<&b7MGb;*<3f+}9 zdx^BLHZreVRphami@BoIs$wTsQfG2rN^OqQROvRSN(&0CwtQ87nj@v$3I(%7Qy!O- znQF~6=9y#TZF%XX@s?x^F0iSxvvRX=RC&3^B%`^=YIhepq;&LyhZJVJxhP*{E-QDN zOL51zt;N>z(&A!wvD;RgS7NNQ)aTaa=agHK62xV-I}zA-0;XQ%sIDzeE6RvT_vi}> zbBhZL%PQOnWyW%&Bgdo9EvRx@^QDyqg?f*n#88uMN+>cp@*tI%>qcPag~7DXbgGGk zv0EJ~jsP3BeU{9t6kbx$Cr>@qxr98~HKA*QvIph#J8~<}YP=J@6Gh%W!gNJdMrlF< zO{D`8w{mpRA0qIn05+Q-ayguCx2ibDWpHw>bDpbtSzV~<#*%>9JD*HK`e@oE-rZc> z+5)gg0O!xVZ1d(>?Qeq9fL1*6qYZ_lG0gAx`|2<8f4s@zEq%J^$@8LxQ@m&YRx3e4Z z?JP!O7h96RUf6GpY`YA9l&poohiH@0v00!BQk?Mn*-s!j)g!F)rAGJ;{=<@MQQv*9 zs6Y+6m~zVlS7=bzEW}{_!SS#pX`JGwjno5%6yJXoRjXI6E-YQc z+v2QoajF-ihAo-Q-Ou~+jSt32@pHfYcnmlLuttBM1sR4i08_Q0;x!M#$wE4uER;_U z6&--*Nv3H;$w@JpOh?mI0kq3HvL4wq1N7f^7uQx-aa&uOHk?$C(a6J2ZfM%t!c|w* zx_3cT(U4TZlMQqOR5*)bcdEJ;dKW3SXXxS+xR$Lu4lPx)bmY=QJ6g8#@d-LZR8RBr z$#etRa94(0)1Pguqw854&~5a|yA~-vjrv%7hGScaPA{6Wdy;zml*olze#AGTch59m zyP>)s)v@R_7wD$kerD0jyCw5BzZzMYrby7YL3jE2-fkFbgTN3CM`cTAL0YgP3?y` z%=8|B7qPI+dkP~Z;$8?m0PrE-DEt=hUXjqhU2-BlYOulQH|4I7r`?*#; zKBCo~r$-Ii{k(eEr069J`2|a&COId#p1CrRJoKt`(XF4i|EPZV zq`T?}?_kp9Ii%0aN_iXY^KxcRc6Lr?NY(14?uF{-2X0?9il+~~I&EO)Guab%O**?+ zy7b-a(ch_m{MYW&w|MgKM_2yk`pwpo+mg8lYN*x%D|z^Rr5BwN8C^_KxCryO3!X%K zWs#@SRuf{a&a6rDNJHsL?*K*Y&UG29)N@{~Iy8r;y*}Ld>&17abq%e>Z>uj{P1tae zC%vX095830bW~TGV#A!N33Ju*7gwz-uG`7ac^AaaULQ&iUHqu(qWbODjMxVL;=+vs zXs_u~GEML*Km)g0^KG_}LZ_p!u&|*)d1GZ-mM9@39hwn(B**Dk1q~mvvmvW6r!cE9 zB+qKfH`y`^l?d#Jh=>u1;1|s5()l|?LMPyR{u|_TJ0vY^1tCzk{&4YzWy!h?Jh>^6cQE-e@!~1lHf`el&tdybo3>3^tm zwutHc1QZ92P{>=MdVESkfAmgL1YQYQNX>Td27HB!p<$p_CWM0-5-yRqc!M8wDj4?{ z@EG~!?k^-jq7`7Qnz3dS6FJ6fDjA~qX49?L-{X*X`%H9r!=l=SYWncVk@R70t@iK+ zUKr{h3VC^Gv8b!ho~_1nj)V6P6^4pZ%M8^uszbY)4%XCUrj{W(=Kl=*;rb<7)u`8B zAGK(4{lSe~SGl)BwBp!~%O9#pKV&l{fwHC?&0gCwu8$kte-;U83qiu*C23AuvZFy3gWr z2wcgrqD3ltNYO2%rH2�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 DgjuED diff --git a/demo/public/fonts/awesome/fontawesome-webfont.eot b/demo/public/fonts/awesome/fontawesome-webfont.eot deleted file mode 100755 index 6cfd56609567bc9db55186415c694d1d32808fc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72449 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
    U~M!&>g0b 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 diff --git a/demo/public/fonts/awesome/fontawesome-webfont.svg b/demo/public/fonts/awesome/fontawesome-webfont.svg deleted file mode 100755 index a9f8469..0000000 --- a/demo/public/fonts/awesome/fontawesome-webfont.svg +++ /dev/null @@ -1,504 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/awesome/fontawesome-webfont.ttf b/demo/public/fonts/awesome/fontawesome-webfont.ttf deleted file mode 100755 index 5cd6cff6d6f6cf438a882e366420dbcc5dddd3f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141564 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ƓCICn66&u!PHl6pn& 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%0D%8GQ!Ai?6y^fVUY%4hi>cI9O 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
    KT23~?3Dw&p)A=062yj$!;@;Sv13 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 - - - - - - - - - - - -Open Sans Regular Specimen - - - - - - -
    - - - -
    - - -
    - -
    -
    -
    AaBb
    -
    -
    - -
    -
    A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
    -
    -
    -
    - - - - - - - - - - - - - - - - -
    10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    - -
    - -
    - - - -
    - - -
    -
    ◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
    body
    body
    body
    -
    - bodyOpen Sans Regular -
    -
    - bodyArial -
    -
    - bodyVerdana -
    -
    - bodyGeorgia -
    - - - -
    - - -
    - -
    -

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    -
    -
    -

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    - -
    - -
    - -
    -
    -

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    -

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    - -
    - -
    - -
    -
    -

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    - -
    - - - -
    -
    -

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    - -
    -
    -

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    - -
    -
    -

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    -

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    - -
    - -
    - -
    -
    -

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    - -
    - - - - -
    - -
    - -
    - -
    -

    Lorem Ipsum Dolor

    -

    Etiam porta sem malesuada magna mollis euismod

    - - -
    -
    -
    -
    -

    Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    - - -

    Pellentesque ornare sem

    - -

    Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

    - -

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    - -

    Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

    - -

    Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

    - -

    Cras mattis consectetur

    - -

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

    - -

    Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

    -
    - - -
    - -
    - - - - - - - - - -
    - -
    - -
    -
    -
    -

    Installing Webfonts

    - -

    Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

    - -

    1. Upload your webfonts

    -

    You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

    - -

    2. Include the webfont stylesheet

    -

    A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

    - - - -@font-face{ - font-family: 'MyWebFont'; - src: url('WebFont.eot'); - src: url('WebFont.eot?iefix') format('eot'), - url('WebFont.woff') format('woff'), - url('WebFont.ttf') format('truetype'), - url('WebFont.svg#webfont') format('svg'); -} - - -

    We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

    - <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> - -

    3. Modify your own stylesheet

    -

    To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

    -p { font-family: 'MyWebFont', Arial, sans-serif; } - -

    4. Test

    -

    Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

    -
    - - -
    - -
    - -
    - -
    - - diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot deleted file mode 100644 index 6bbc3cf58cb011a6b4bf3cb1612ce212608f7274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19836 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf deleted file mode 100644 index c537f8382a42986cc5e0d5a06b1460df4f0a5e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38232 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 S)0Gzotc-JM?Hi4}ZTDZPiC;_r diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff deleted file mode 100644 index e231183dce4c7b452afc9e7799586fd285e146f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22660 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 ML;wL%uV=LY00kR;TmS$7 diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png b/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png deleted file mode 100644 index 8dfaed30ad56f7273d757737b7498bd2844d5557..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84913 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 -

    - -
    -
    -
    -
    -
    -
    -

    - Bilan 2014 de l'hébergement -

    -
    -
    -
    -
    - Date - Sat 20 December 2014 - - Tags - Hébergement -
    -

    Déjà une année que j'ai migré l'hébergement de mes services d'un serveur à la -maison vers un hébergeur, en l'occurence OVH et son VPS Classic 2, sur un -container OpenVz dédié.

    -

    Les services hébergés sont à peu près les mêmes avec quelques nouveautés :

    -
    -

    J'ai commencé à utilisé Wallabag depuis quelques -semaines, c'est la pièce qui me manquait dans mon processus de veille, entre le -lien récupéré en vitesse et avant l'ajout du lien dans mes favoris Shaarli s'il -en vaut la peine. Je l'utilise dans le cadre du projet -Framabag mais je prévois d'installer ma propre -instance d'ici peu.

    -
    - -
    - - - - - - - - -

    - - - - - - - - - - - - -
    -
    -
    -
    -
    - -
    - -
    - -
    - - - - - - - - - - - - diff --git a/demo/public/js/markdown.js b/demo/public/js/markdown.js deleted file mode 100644 index 65b04e4..0000000 --- a/demo/public/js/markdown.js +++ /dev/null @@ -1,1740 +0,0 @@ -// Released under MIT license -// Copyright (c) 2009-2010 Dominic Baggott -// Copyright (c) 2009-2010 Ash Berlin -// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) -// Date: 2013-09-15T16:12Z - -(function(expose) { - - - - - var MarkdownHelpers = {}; - - // For Spidermonkey based engines - function mk_block_toSource() { - return "Markdown.mk_block( " + - uneval(this.toString()) + - ", " + - uneval(this.trailing) + - ", " + - uneval(this.lineNumber) + - " )"; - } - - // node - function mk_block_inspect() { - var util = require("util"); - return "Markdown.mk_block( " + - util.inspect(this.toString()) + - ", " + - util.inspect(this.trailing) + - ", " + - util.inspect(this.lineNumber) + - " )"; - - } - - MarkdownHelpers.mk_block = function(block, trail, line) { - // Be helpful for default case in tests. - if ( arguments.length === 1 ) - trail = "\n\n"; - - // We actually need a String object, not a string primitive - /* jshint -W053 */ - var s = new String(block); - s.trailing = trail; - // To make it clear its not just a string - s.inspect = mk_block_inspect; - s.toSource = mk_block_toSource; - - if ( line !== undefined ) - s.lineNumber = line; - - return s; - }; - - - var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - // Don't mess with Array.prototype. Its not friendly - if ( Array.prototype.forEach ) { - MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { - return arr.forEach( cb, thisp ); - }; - } - else { - MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { - for (var i = 0; i < arr.length; i++) - cb.call(thisp || arr, arr[i], i, arr); - }; - } - - MarkdownHelpers.isEmpty = function isEmpty( obj ) { - for ( var key in obj ) { - if ( hasOwnProperty.call( obj, key ) ) - return false; - } - return true; - }; - - MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { - return isArray(jsonml) - && jsonml.length > 1 - && typeof jsonml[ 1 ] === "object" - && !( isArray(jsonml[ 1 ]) ) - ? jsonml[ 1 ] - : undefined; - }; - - - - - /** - * class Markdown - * - * Markdown processing in Javascript done right. We have very particular views - * on what constitutes 'right' which include: - * - * - produces well-formed HTML (this means that em and strong nesting is - * important) - * - * - has an intermediate representation to allow processing of parsed data (We - * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). - * - * - is easily extensible to add new dialects without having to rewrite the - * entire parsing mechanics - * - * - has a good test suite - * - * This implementation fulfills all of these (except that the test suite could - * do with expanding to automatically run all the fixtures from other Markdown - * implementations.) - * - * ##### Intermediate Representation - * - * *TODO* Talk about this :) Its JsonML, but document the node names we use. - * - * [JsonML]: http://jsonml.org/ "JSON Markup Language" - **/ - var Markdown = function(dialect) { - switch (typeof dialect) { - case "undefined": - this.dialect = Markdown.dialects.Gruber; - break; - case "object": - this.dialect = dialect; - break; - default: - if ( dialect in Markdown.dialects ) - this.dialect = Markdown.dialects[dialect]; - else - throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); - break; - } - this.em_state = []; - this.strong_state = []; - this.debug_indent = ""; - }; - - /** - * Markdown.dialects - * - * Namespace of built-in dialects. - **/ - Markdown.dialects = {}; - - - - - // Imported functions - var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, - isArray = MarkdownHelpers.isArray; - - /** - * parse( markdown, [dialect] ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. - **/ - Markdown.parse = function( source, dialect ) { - // dialect will default if undefined - var md = new Markdown( dialect ); - return md.toTree( source ); - }; - - function count_lines( str ) { - var n = 0, - i = -1; - while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) - n++; - return n; - } - - // Internal - split source into rough blocks - Markdown.prototype.split_blocks = function splitBlocks( input ) { - input = input.replace(/(\r\n|\n|\r)/g, "\n"); - // [\s\S] matches _anything_ (newline or space) - // [^] is equivalent but doesn't work in IEs. - var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, - blocks = [], - m; - - var line_no = 1; - - if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { - // skip (but count) leading blank lines - line_no += count_lines( m[0] ); - re.lastIndex = m[0].length; - } - - while ( ( m = re.exec(input) ) !== null ) { - if (m[2] === "\n#") { - m[2] = "\n"; - re.lastIndex--; - } - blocks.push( mk_block( m[1], m[2], line_no ) ); - line_no += count_lines( m[0] ); - } - - return blocks; - }; - - /** - * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] - * - block (String): the block to process - * - next (Array): the following blocks - * - * Process `block` and return an array of JsonML nodes representing `block`. - * - * It does this by asking each block level function in the dialect to process - * the block until one can. Succesful handling is indicated by returning an - * array (with zero or more JsonML nodes), failure by a false value. - * - * Blocks handlers are responsible for calling [[Markdown#processInline]] - * themselves as appropriate. - * - * If the blocks were split incorrectly or adjacent blocks need collapsing you - * can adjust `next` in place using shift/splice etc. - * - * If any of this default behaviour is not right for the dialect, you can - * define a `__call__` method on the dialect that will get invoked to handle - * the block processing. - */ - Markdown.prototype.processBlock = function processBlock( block, next ) { - var cbs = this.dialect.block, - ord = cbs.__order__; - - if ( "__call__" in cbs ) - return cbs.__call__.call(this, block, next); - - for ( var i = 0; i < ord.length; i++ ) { - //D:this.debug( "Testing", ord[i] ); - var res = cbs[ ord[i] ].call( this, block, next ); - if ( res ) { - //D:this.debug(" matched"); - if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) - this.debug(ord[i], "didn't return a proper array"); - //D:this.debug( "" ); - return res; - } - } - - // Uhoh! no match! Should we throw an error? - return []; - }; - - Markdown.prototype.processInline = function processInline( block ) { - return this.dialect.inline.__call__.call( this, String( block ) ); - }; - - /** - * Markdown#toTree( source ) -> JsonML - * - source (String): markdown source to parse - * - * Parse `source` into a JsonML tree representing the markdown document. - **/ - // custom_tree means set this.tree to `custom_tree` and restore old value on return - Markdown.prototype.toTree = function toTree( source, custom_root ) { - var blocks = source instanceof Array ? source : this.split_blocks( source ); - - // Make tree a member variable so its easier to mess with in extensions - var old_tree = this.tree; - try { - this.tree = custom_root || this.tree || [ "markdown" ]; - - blocks_loop: - while ( blocks.length ) { - var b = this.processBlock( blocks.shift(), blocks ); - - // Reference blocks and the like won't return any content - if ( !b.length ) - continue blocks_loop; - - this.tree.push.apply( this.tree, b ); - } - return this.tree; - } - finally { - if ( custom_root ) - this.tree = old_tree; - } - }; - - // Noop by default - Markdown.prototype.debug = function () { - var args = Array.prototype.slice.call( arguments); - args.unshift(this.debug_indent); - if ( typeof print !== "undefined" ) - print.apply( print, args ); - if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) - console.log.apply( null, args ); - }; - - Markdown.prototype.loop_re_over_block = function( re, block, cb ) { - // Dont use /g regexps with this - var m, - b = block.valueOf(); - - while ( b.length && (m = re.exec(b) ) !== null ) { - b = b.substr( m[0].length ); - cb.call(this, m); - } - return b; - }; - - // Build default order from insertion order. - Markdown.buildBlockOrder = function(d) { - var ord = []; - for ( var i in d ) { - if ( i === "__order__" || i === "__call__" ) - continue; - ord.push( i ); - } - d.__order__ = ord; - }; - - // Build patterns for inline matcher - Markdown.buildInlinePatterns = function(d) { - var patterns = []; - - for ( var i in d ) { - // __foo__ is reserved and not a pattern - if ( i.match( /^__.*__$/) ) - continue; - var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) - .replace( /\n/, "\\n" ); - patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); - } - - patterns = patterns.join("|"); - d.__patterns__ = patterns; - //print("patterns:", uneval( patterns ) ); - - var fn = d.__call__; - d.__call__ = function(text, pattern) { - if ( pattern !== undefined ) - return fn.call(this, text, pattern); - else - return fn.call(this, text, patterns); - }; - }; - - - - - var extract_attr = MarkdownHelpers.extract_attr; - - /** - * renderJsonML( jsonml[, options] ) -> String - * - jsonml (Array): JsonML array to render to XML - * - options (Object): options - * - * Converts the given JsonML into well-formed XML. - * - * The options currently understood are: - * - * - root (Boolean): wether or not the root node should be included in the - * output, or just its children. The default `false` is to not include the - * root itself. - */ - Markdown.renderJsonML = function( jsonml, options ) { - options = options || {}; - // include the root element in the rendered output? - options.root = options.root || false; - - var content = []; - - if ( options.root ) { - content.push( render_tree( jsonml ) ); - } - else { - jsonml.shift(); // get rid of the tag - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - jsonml.shift(); // get rid of the attributes - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - } - - return content.join( "\n\n" ); - }; - - - /** - * toHTMLTree( markdown, [dialect] ) -> JsonML - * toHTMLTree( md_tree ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Turn markdown into HTML, represented as a JsonML tree. If a string is given - * to this function, it is first parsed into a markdown tree by calling - * [[parse]]. - **/ - Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { - - // convert string input to an MD tree - if ( typeof input === "string" ) - input = this.parse( input, dialect ); - - // Now convert the MD tree to an HTML tree - - // remove references from the tree - var attrs = extract_attr( input ), - refs = {}; - - if ( attrs && attrs.references ) - refs = attrs.references; - - var html = convert_tree_to_html( input, refs , options ); - merge_text_nodes( html ); - return html; - }; - - /** - * toHTML( markdown, [dialect] ) -> String - * toHTML( md_tree ) -> String - * - markdown (String): markdown string to parse - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Take markdown (either as a string or as a JsonML tree) and run it through - * [[toHTMLTree]] then turn it into a well-formated HTML fragment. - **/ - Markdown.toHTML = function toHTML( source , dialect , options ) { - var input = this.toHTMLTree( source , dialect , options ); - - return this.renderJsonML( input ); - }; - - - function escapeHTML( text ) { - return text.replace( /&/g, "&" ) - .replace( //g, ">" ) - .replace( /"/g, """ ) - .replace( /'/g, "'" ); - } - - function render_tree( jsonml ) { - // basic case - if ( typeof jsonml === "string" ) - return escapeHTML( jsonml ); - - var tag = jsonml.shift(), - attributes = {}, - content = []; - - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - attributes = jsonml.shift(); - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - - var tag_attrs = ""; - for ( var a in attributes ) - tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; - - // be careful about adding whitespace here for inline elements - if ( tag === "img" || tag === "br" || tag === "hr" ) - return "<"+ tag + tag_attrs + "/>"; - else - return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; - } - - function convert_tree_to_html( tree, references, options ) { - var i; - options = options || {}; - - // shallow clone - var jsonml = tree.slice( 0 ); - - if ( typeof options.preprocessTreeNode === "function" ) - jsonml = options.preprocessTreeNode(jsonml, references); - - // Clone attributes if they exist - var attrs = extract_attr( jsonml ); - if ( attrs ) { - jsonml[ 1 ] = {}; - for ( i in attrs ) { - jsonml[ 1 ][ i ] = attrs[ i ]; - } - attrs = jsonml[ 1 ]; - } - - // basic case - if ( typeof jsonml === "string" ) - return jsonml; - - // convert this node - switch ( jsonml[ 0 ] ) { - case "header": - jsonml[ 0 ] = "h" + jsonml[ 1 ].level; - delete jsonml[ 1 ].level; - break; - case "bulletlist": - jsonml[ 0 ] = "ul"; - break; - case "numberlist": - jsonml[ 0 ] = "ol"; - break; - case "listitem": - jsonml[ 0 ] = "li"; - break; - case "para": - jsonml[ 0 ] = "p"; - break; - case "markdown": - jsonml[ 0 ] = "html"; - if ( attrs ) - delete attrs.references; - break; - case "code_block": - jsonml[ 0 ] = "pre"; - i = attrs ? 2 : 1; - var code = [ "code" ]; - code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); - jsonml[ i ] = code; - break; - case "inlinecode": - jsonml[ 0 ] = "code"; - break; - case "img": - jsonml[ 1 ].src = jsonml[ 1 ].href; - delete jsonml[ 1 ].href; - break; - case "linebreak": - jsonml[ 0 ] = "br"; - break; - case "link": - jsonml[ 0 ] = "a"; - break; - case "link_ref": - jsonml[ 0 ] = "a"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.href = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - case "img_ref": - jsonml[ 0 ] = "img"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.src = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - } - - // convert all the children - i = 1; - - // deal with the attribute node, if it exists - if ( attrs ) { - // if there are keys, skip over it - for ( var key in jsonml[ 1 ] ) { - i = 2; - break; - } - // if there aren't, remove it - if ( i === 1 ) - jsonml.splice( i, 1 ); - } - - for ( ; i < jsonml.length; ++i ) { - jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); - } - - return jsonml; - } - - - // merges adjacent text nodes into a single node - function merge_text_nodes( jsonml ) { - // skip the tag name and attribute hash - var i = extract_attr( jsonml ) ? 2 : 1; - - while ( i < jsonml.length ) { - // if it's a string check the next item too - if ( typeof jsonml[ i ] === "string" ) { - if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { - // merge the second string into the first and remove it - jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; - } - else { - ++i; - } - } - // if it's not a string recurse - else { - merge_text_nodes( jsonml[ i ] ); - ++i; - } - } - }; - - - - var DialectHelpers = {}; - DialectHelpers.inline_until_char = function( text, want ) { - var consumed = 0, - nodes = []; - - while ( true ) { - if ( text.charAt( consumed ) === want ) { - // Found the character we were looking for - consumed++; - return [ consumed, nodes ]; - } - - if ( consumed >= text.length ) { - // No closing char found. Abort. - return null; - } - - var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); - consumed += res[ 0 ]; - // Add any returned nodes. - nodes.push.apply( nodes, res.slice( 1 ) ); - } - }; - - // Helper function to make sub-classing a dialect easier - DialectHelpers.subclassDialect = function( d ) { - function Block() {} - Block.prototype = d.block; - function Inline() {} - Inline.prototype = d.inline; - - return { block: new Block(), inline: new Inline() }; - }; - - - - - var forEach = MarkdownHelpers.forEach, - extract_attr = MarkdownHelpers.extract_attr, - mk_block = MarkdownHelpers.mk_block, - isEmpty = MarkdownHelpers.isEmpty, - inline_until_char = DialectHelpers.inline_until_char; - - /** - * Gruber dialect - * - * The default dialect that follows the rules set out by John Gruber's - * markdown.pl as closely as possible. Well actually we follow the behaviour of - * that script which in some places is not exactly what the syntax web page - * says. - **/ - var Gruber = { - block: { - atxHeader: function atxHeader( block, next ) { - var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var header = [ "header", { level: m[ 1 ].length } ]; - Array.prototype.push.apply(header, this.processInline(m[ 2 ])); - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - setextHeader: function setextHeader( block, next ) { - var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var level = ( m[ 2 ] === "=" ) ? 1 : 2, - header = [ "header", { level : level }, m[ 1 ] ]; - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - code: function code( block, next ) { - // | Foo - // |bar - // should be a code block followed by a paragraph. Fun - // - // There might also be adjacent code block to merge. - - var ret = [], - re = /^(?: {0,3}\t| {4})(.*)\n?/; - - // 4 spaces + content - if ( !block.match( re ) ) - return undefined; - - block_search: - do { - // Now pull out the rest of the lines - var b = this.loop_re_over_block( - re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); - - if ( b.length ) { - // Case alluded to in first comment. push it back on as a new block - next.unshift( mk_block(b, block.trailing) ); - break block_search; - } - else if ( next.length ) { - // Check the next block - it might be code too - if ( !next[0].match( re ) ) - break block_search; - - // Pull how how many blanks lines follow - minus two to account for .join - ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); - - block = next.shift(); - } - else { - break block_search; - } - } while ( true ); - - return [ [ "code_block", ret.join("\n") ] ]; - }, - - horizRule: function horizRule( block, next ) { - // this needs to find any hr in the block to handle abutting blocks - var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); - - if ( !m ) - return undefined; - - var jsonml = [ [ "hr" ] ]; - - // if there's a leading abutting block, process it - if ( m[ 1 ] ) { - var contained = mk_block( m[ 1 ], "", block.lineNumber ); - jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); - } - - // if there's a trailing abutting block, stick it into next - if ( m[ 3 ] ) - next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); - - return jsonml; - }, - - // There are two types of lists. Tight and loose. Tight lists have no whitespace - // between the items (and result in text just in the

    9?uG;6JXDPtu17tlup#cGgk~?1Nc5Z5wH} zW;0k6d+rjDw$1$69~7|9P9L1yyRCD+TKMccCd9OwA9=v5BonN*+3c2rNlSRL)M2Bb zXTK5fed4*cd5h;=^=bYjg;BgcW{Jy%RpE$+fYv(H*Oj^5R?#R zt$@p$&(%NBYP>i>EStX{*U)d}sq>RYuaKzqC+#pFTl44BnArbY1)D!k?{ouJ6+XTB zbA}20yC8^0qJ2p8Om(EbM{M(DbfyKdvqSgOL4259yD1`f$q(BNpSn8*ajt#3Hq{|K zmoHYK8Db~hht8oTV6=4=kRM1)WHsPvG%*tOpqZo)p>DawlN(7)0W4e*qXf9eXr8cC zo%te#r*Q6Z?j^VTqwjzQmB2QrQDnS+$f_`1%n%}C%HmD8JRU0e1#39Wd3FCkEjm^4 zx$t5OZ|u?_SoL(!OU5WB(bH8ays1_+Neu$W{yAhHdq-Ba8i?y%)!eQzPbPabz7MCU zBVqL>`_-=tGS$Z0z$4AUVc@WuRoR*adp@Kd3KRaob>X597LMCmCGhO|N^D!P!;oBi0ghiLNE+vle zzbW&q&vZsj)yL>$6c0`70j@)U#v!UD;I6=(6f)%>=1}+L%h> z2M%L>mXpKFEn%(1~KV|rvB_|z&bu*_NkHek*q zvu*AOR7+b&TOw_1;Tgn%K^ z-?mXVA7~R269}cs`|1=_PW!<01eg&rO)L+}R+)ny=eC?>r2|y}&@Bg(Q)_i<(B?0O zeNs$v?=LyCS{7ARyT1rib-0bN+48*&xm+{K)p?naK_PN$@^!EK-&F+h&nTCa)?=aoREG;4kP(oEB;gJ8&>8ypVEr`k5mA9Wc3473!j)&F* z*(}2$wN|%j9)G0OH=9{weqASh=D}aAP^-HM#oiC0{~kkv_8`er3d%#g_@L$;Q99EB z4*Qtz#g1uBv+Lpdr6awY--$aS2@=ai{#48{vEAy~n5wRB3O)K~6#degDgTwwzJd}U z{AbIZc?VVYc852W!wYS|q0@q6|s+2Ldx8hMATgOX_1z z&QP_4|FGgBc>NKg5Y64of0K3p zpX*ru@7BHiUul2tB#iKQyM6jXweByo(SCoO@$mArEsWYmZ4H>@qnj! z53hymz3LbYGFN)k_)9ok(i326i zrB5dKYdpsS+v>^QBuy z$=s+bFK*HUfs{qM`NvY&mQCS$OW{|PF74J28NiC9bcF-SBfqnwdHr)U-OPJQq(LZc z5;u8lzFr-;s-R=R%$pm<#zwn>-N4D=VZ&>iL937VVY=+HE=lSH0NgUFY;@uLTYIVn zc=n~h_7RvLnsZ-6=AB+?+UR0}?)paWPO)@8nPYqaGE|-4VTNCKQEN*1_)xcaZom1R zd$Cbp)xJ{W-%Uy#W{l8})j0PrYxqB=8y!L+qzl+-(hu%$J5sCrAvs%NwxWLwrksH`Zz~TO8#ZI3TM3T-=@Y zL9mQtw{f^bYRjF2k%IE{{a|CnI6jI7+kZ(u9zCf&3*X$=boyLJW~-FXnzB|L-KJd! zBFk>ug!biq^8O_Sq;Fbj=%JnJNVz6+W0HVQnHsl0OGbf!1uy;VKb~+Nr3j^j|2Fu1 zygA_$3@=J(=0O{V*KS?Y@eX~gJsx3cwq#sYrAoTca9bW+cJR#vB?$lGng`8j?+XqM z6g->6hE5p51|!6>#AHt<6$wJMm7JV(YC!(N9Zh~^d(O9QL8qPh*?e-zcnX(@*%IWx zLED`_wb}!uI{v8ZQziHNfJO5)5%Ff$Y8h{lkd}qKHyqGP(kyX`|!Ckqd02^zkTlTO^gT^vRW0>#7V#Mk7mPV-sogw+n zu5~+`LzhF&2>XDP_!B{!5#YwlMd*R9Z6rut`=o*IpvrQxfkqIh>PAz&KV@pYFpT=GOdL< z-$n1@wr{&Qgpy1JEGfEZ5rWcUoKM7#Zm6I@sX?4$C!rxXshkt172`{hVf4nrRr>|B zv_4`M@MF2*^n<7|IS)bLHH`zEc38j#;fkIUixp{q-pajITD0VPbTj?y&YJ7(F{Bjv zaBs$ZWrOc6nI8@3g>>VM-1t{&EcO>0aYhh98{wSF%8r~}4G7p%CqG}EDBtKM4@ukD zP7${5vp*tkK)6TzVKPiKQU>oZu(_!d@LTn`gHf%RnQmtHx~3%DQBM7iZ6DixacOBZ zf4J9qg+!X7Ci8#LsSr=!sA*o7x-Y=m=Gs;bW18RgDZoVlqU1#)h2Gmil{Dwd*Ee(@ z4o;uPNR$>_Y3qZXHD!R!BNp8f{i)G|`~8V=!qtg0YslC|p5*GyaV*ImofdnRc}-S; zd=YUtr^+f><5q3nv|Gz*c3azl*ni4NWdT2B8v>zLxKIjoYA-Ig$Y0+#^1)ml>1DE@ zLh$yj-0whD{#>OiK-IikQpnLZaI2KTMKRcQ#N4xxzZ)M*f@-ziHUfXTa;1qd?;c`{(4t@B|c%r-8;i1;yhf2(Q$wyBEO(7BGMss8+MOUze z$17r%Q@xb-6;KLUjI7NvV>YQXs_eqA`jRGey*T#P6BnW?ic6xJU;b6>A(^gZW$%e6G~#@aUAS<^ZX=l)nX^J3t09Eh|~^s0B@kXlbWj@8@# z?DjRY791u$!7rBVYcEUk*pWc5Zk*S-&{kBfN)n2-F|)j>bjHnJsk+n&ddyfl}Mq3zl|k z5|j0_yq!Mm3>do!$(w1?&K3bSwOhHk2UKKOyw%N8+N~GxPZYMJdmK6YcN0S|ha_w` z*tJW{R8?fwP71CU-o)}Lu9-!m;ueOB3sej`(a2;D1#9M!@@B=FVh}dE3c7f_LCN2- zwrNm4_5hD3w1$g)*8}f#%~5gd>xfr@Csd)1D|Ezccu`wA4}E!BDSA<1cRMysP#KSv zIz5a9_6C?6ToEW{E=o2lyU+;YDSkjSINg)8Q$UZF{JbI;T##k{f#RJ5To2X@e~(bd zlmUkTpiEx@1LN}p3CD8hI*Ksz(Aw~%Aq|=ZGdTj1}r5xcU$s!O@2+tGJU&(EtZQX;oKb{{~K3Ko;tQU z{IT)^3xi=(#U|*O0ehIFY}&j=hrQuV33p>DR*^RY?{AOPN1y3<v57+V= z;~6ZT&o`2M`Ga%w)_I5-UTEjuY|X*^Kp8XTja0QZTEX7OH*eJ&E^lGzW@~)4ESRil2Vs(~S1o@%(3yR2dU)4Q(C(}oYw+4tW0&2fnvRdq> zcLoH3IiG13(%$z>s%Y9tP`nq82%!*4R+&`S@4Lpze>EuPJ8L+})!^ovz#<^m9ko(n zrs29&7Q4EyE^B{flCM+3uOH>aYtBqxWZOp5RzRxs*R$Wp`&HSkm34M%;onKhh~D$l z8$tCTSbN2eS7zo>bYw#jefw$lDl5R!W)}(N-+5JhL1}OenaO#1_=FdEie%6v`|}|T zPv<9FV|JWubFF3Zgya2>4e8)t*&<2a;r%A#TdE8sIA>|fHwk1$6y2acbin`Xk zZ5fE_omV+GCX)7=#eLMz(_1wG+2ZwTr!iN(g$`umNTbVWy1zT=lox z(wcWHqMkW{bp$zg?&MVLrVam(@YS<&$6p5j5q$-E(1s?zUW9{g+r}Psl>?KQ?X9Q2 z5dibq=2|^9KbgtL0l zN7kQH*Bh8LTew#C7o7$(vp^9XH?`4NYyxEKOu)ZH7+a^xRu?lqplf{CU_9zw(cAU@ zNmH+@N5b=oK%nH(g=$Hr$=|AOA7w&n$zGy)ggR6oEn_Phmbw_0f2^nJQbav<(|l45 z$li&o==W~%bdAwoqydIR`3<+q*iSHot$Ezia?^yUv~L_}Rwl@{oeyz5xc4DBfV~bV zh-I5oE9?#}vzmqqCU0Bkz+rtI(<_N;q=jQqpDxgJS3gf&K8MLI=-OdH611bacgc*a z8z5<`2T$Y_2rTyg2?pN6$KM#6wj`q*D?a0wYzyX>^CBF$`aC3ZYLv0SML)SixjNWZo+^qdbQ;f7aXEbCxl$}ABrt#MHKU~qEB=)V|}IFjwRi;a7+abz^uPrLr^ zM-lJ$!sZ+WTyk!pcJB6AoX}J;Dt{?eK2%T3xrn+tm7o=PU#%Z8C$Upa8$XIdLx5v54|ls4jE@8 zx2b%ow5(VkmRr%GoGKIR;8Qlc*SCvlA%1XH9XbynwINg#Q2FUdZnMDM;=%)`%MtO= zA@<*CtjIm8Fl1bqRhY!^{^^+{VFFlil%G756V<$y*30V1z0P|M%y0EpS~c3}sl%Q% z2vMmnUj}}vzGpPI`VOi&jnDd0a}?5Sz3^^$(Y~cl!j`hNic~LJ{zlydqb}Y_-)MkO z!Lt|b{hW4wMqqg~-#nh5bd_yS>rS2~O zOn+nU*AxSaes);{j$*G$6DkN*+6~*sOyrjpoMH5TxE-EEkS6B*?4cglos$(~8HKB4 z4mSp4NB(Kf)U`JUy7gjf{5u7}X52E+E|dT=)9j()r2@&km|9s1UO+5q2uX3bi4G0H>m!wwSaetZgkERge{VBqcha{zAK*c< z&;t@P>B|*BlVWJAG)R+iDZhtaaS2S;VC(bKkbhp%<usm&x6-FZYJ;=AiiWElim8(2+!a{+l)ntis0OGL&`2W1 zyb>GXj*!f}F4-9Jd1e99z=<+^lIKYJJ4TFTUNi%^wzTa_4ldrOIDBI9eI5w*R3Gi|nbSUf0VA z6genYdHQ}5oWF9SW1O&0Y~VHv_Uz6Z{P^HQd{ZA$2V6FHX?qp^KuATcAU>a7C(Xab z37;W&wSJO%npr|6z%iaio#rSqepP>svf@dcHy+NyY?f5&Y57(PwgyOJgd~<%@eeC2 zPx`V64IEf0$Cz5Y1Z$_pW40woz7Oq^nBHu6gJsy>6;Cxb3h{0D1+TsItqc3r$hkfH zeu!rR^7oR0bC;HNx98phOQp606l`1+?fx|}id zyB*_LvdWtK?fXV=zm0QoTgXSc-o|{DAF0qTqS5L%+x>d*8|(Jwb&5<{p2Vbu2CQ}N+;hR6r_YOlciG&26`3}3;@{mXo=0p8ls-c0w8I6@_nhZAuQW5s9k@1W za@s5Ewe5aAcM{grJO%XVu&sdVUgWlaj5)b21#fc^Hu+GQz8VZfUpn-@(#o}GO`+8c z-3epyMv2>=46N@Uej;|;hbY^xxk^4xn@9VKUt;FtPKyG%zHoyU5c9W=5*Qlw*C8}` z9|l(>cgd>erZ>%DzZvxOZCJDMT-9R+!jm{!eMh8bz}TSSUsu792BTeW?g|xSnC<7S z_M|{((-hdZi(_d+oNMjBWGNx+U~&&|KZC6?yE$F+XAX$&MD?_p`%04$_`=fSZE*!b zUnr%slb`HSf+W93TU|og-=92#uxVW|cvFF0g3MmCLf!!Hvvy4u zOz>I+q^d1cArBBBOOj)2_LWsx0*}EG0HILSR>1|y@{tkX*t+oAIZ{`w7M47WAV+Zy zH>Q^?`GsW3-)n8(XJ>A4?Pk;Mj|(}uIeF^DuV*9nTN@&Bb0LIBp`9YoCI15R)C@ew z$qomF;<97ip7C7H`moJoq}|RKdnUrO(JRfj8Io%7Z=22}A?y?gYd-;vp`lZTN|{A_ zxHSfcm&ttZu;6)DKMD`OcY5owYx}UN$5>Z*2R=G`zeBzEv7SZ*II^OjJ{WcU#pu;Hg4i{VwYJF^}W&@Uy0JOBBq%_ll|7m)7G-+Q+I zz(#W{{twZ-|DU3Z|6lwcpI3gqFIf;+)yp$XF&=)+`k&VsLe>^im%s3{f$*K|&UUtN zdgqw!frbBO0oZRLP6&KUEsXSE)2wi~?Y^yDA&J~>zkt%(AEmJ{Ckn=Wb}WpDI*5-d z4o)e!Uo`VO0u%*qxBSjDWehyxb?v|Nm8Hxk*ROwx-sKxONduhgpW1q|_!$wk>TX}( zxxe=3C$GQ^^><>!y0aMrTTGvtUQT=MZsv)u`IMpzShv-R!UBOkI{IxoXwLoQJB_GH zQK#Iz@KWVN+uXNYdkojyC-{CZ6Ol2q?+BSOOCYcg&yCFl5s!yw@B1^7FRbUStkSy9 z{bQ~EE7LJ|zyyJfCON$r6mGA8v<1Q4smOs0Q zO*@zI%l*_Sh-yN719Le{gzMMiA&@Qa-eY}>rR^2$=@ZWl@3FCE%2v*t`SbyqrwLMz zo3MrZF!J<6Pf{#Ek}24I(M90yaCxyp53Z)t2*D?P61N=V!-sziM&8-t0pPdT@E|*w z@fE&c|I+sHadG`&oad2z-sb$Y^ZuwJ1#UE$ZrQ1)c$I3I1cSak13*y5+R@^V!SqGE z4$Xx2dPD^iiJ;Ix|7~I2X3O2c7Z@*~;N)$Yqu>ITPu-HB=E+>fd;8I1&J&K|1{F!= zb$~-*;$4c>>1BtiMA7UhBvQMQSc`nhpj`)TB%=MNw~+Nm_5n`#82{IdETH>w>YFc} zN77kPSX|c6&Fk2rXL<66O?54Y@9X}87X$^Y8+BS{6-w()aTb{gX$j5R0eex9M%`{X z>t5Fhp3meIVyB|M^cKA?=W}1oSE_uaG&bSun4qPyTBhP2ET*8d{LVv53Q|}G=Ab5U_Szm4a4B?Mi?0u}H z_643w`Mn9z+?JlPl<0f2G)gznb~F<@h#McLTWrmY8{X?CJAP zFzJCH9b=+@P^a$bc~7{5%ozV0*pz(FEISyIP&3l25mvX`*usJHU80>IpOo9d)_HxM zD+$mI>{xkT;~`un4QcUgz;neX?zGpHE@QuAra-t6TuvXCMA4jD8`-*9g3~HaAj6b6 z-M)5-;aZ#*0VMJPMnZ^$_oRqV?@>1=z;@mtLDsRQ zQ`P)U_=erWYw^y7q3f7-&%x8V5FEKQWZb!{=b?)Lc4Ag&5CLa8vbW8>h0__@p#DAH zkxhOG81<^CPgA+>S8!_+oU>1a69kypI)kUkB`h-Z-RI2Ym0cTxXC2bWthlX6HBKR+ zRtL;k&pr29_f6x&;sS#WFXpF9b|Z7qsk(+%}->$4HgZw0Wi zrIsqnuWJ3&eSC{9+}d5>2fai66C}S$cwSAZW{tfTLi!gy46I8wc!zI;^3eq`EEbl)%9kryW0`7_|-+w&8j2yaj@pad8<7Z>KUwGkGU40 z)Y}PtY7nF_T6nrunBzOH8GqB%%=^ye{5xJcBc>Ehy1j^N$ba|K4PPG77aVK+mKPRV zbAs^2PB9R{RK&_ce@$!6v1S`wP00rBy)uz)0*WH4M!?7NC`^3t`xz>OlA~mb`IX$< zxGgvZZ8qn%+lN3T?|{<&C_7-3IaY%0p?=IIORPJc?jo5+FY&pS`-0x>5yZA9`I~d< z_cwvFD){q!ZX+kE4JCXdt2~I{o_?yYMdq?Dj@%)L_Qf~vcZrstIkqvNV@C(x!rP6W zl{Yo~-a&BrGk^UfKdFtIuQw2)fIr#8xp*zZ8jJ?b{4H-Y)*qgKeC;C$(@QvMM_PR& zdc$9|ETvuC$g4ztZ3iWbbA+pal&Nz3)-iQ9n&v;?XY8x#yb@YK^b zut%OI!X3Pup4XB4uXth!Q1iXVWEw#8}rRSpMTk(mhIn+(o}h1jvzJBs;rk@ zcDOzt(XD)U_ph7CV9XBYTbxsdr1Sw?B+$1Oi%PzR7cTwnfMg)e9?m{`$eYpfeTS+I zl99K#h)3MC`BFk6o`1(iUScJH{yhvq|F*aAvY_~Urn~_s=a`J^jWe*dnn_=|?~;Wk z7qdjUpJH3lN@|o=b{hM$ElPQjo{q~8CkV$cqj-|;BwNI(K9HDMD%v)ALYwPhfP}&J z)n(er1L(LTU&)E@*FFt{{Z}Vs)y22qjJ^*n(Ycj29`!gs_6U+AGfMcEVCpihyj$tl zdg%jO^*o0wCF^^lPfS=*J|L8()ZckxfJ3s70Ca|s$H*PZAdO*~}t1MQ*+!jJZ^I}RQt7pO= zR!L7caxm|I*%!IG0mhpI6un!nKiYbDmmyCQ9s3&CUBU%^7j<^?j&9QqS48nKMB^Zf zyBD0Wf7)V>7U~`o1)h#n*J63lCTKlv_Yc3?@dV5Bk_B&WjZ!SFk)fs8IkuNj6Y4S? zq^+-MPMwr2dqXC5A-wi6aV1hpy%)bGCPu9Ocyk~BBW!B-4Ug4w?LaIO`T2;>T@RNXdsvSbFE|JW zFSiI*FCjkpz!DJBXDnz=T7U6`nT$u-O;oeY@`|&)j%^H#8|Kjd1gW_o3@KJd2;K60FpuycC1b1EB-Q7L7LxKi(UEJL@NN{%u zZV4^{&XVW%p7;O#d^vTxYHO!zdU|fT`fjp$R{3oO95u=c+$8<2T#o>yhzYLg zf;?;6KPlt=Y<6C(uzm_x;b=<}2PbT@ln7?W|iKYGh zc$EfJ=s_doY2b1qeRvpsM@F-f-R%H=9It*i)Lguf&VLMP+qdeBCr@$o){NHAa-ckI z8ZOcxDwqFL`Onpt+(OXC+9|c0HrfP*yIc7kK<(u`{#KNT$iA@eb+a=S+OulCxLq;M zy}4N%5Zhb1FH+vgFKXiSB`o;z1qE2Cm1(VUv7cu#_otN4q4kQ#OQRKsJmpr_Bep>3j2xogS~QqYtL&<`Y5lpiARqR)5~&u8FRI(VLm|x=5*xBTL_*9p9#=LpV&?3Qp$)^4)1vmvGLF+ zoW>e|EVnJa92@EUTB@`o`0`66dTy4axpR5qC*Nmc2W52sPg;`f%V?WC=`iSWbv&8# zSoWj;hoZ)BZMa6ehacxYWZcoff>I0v?6=4AP%Ku+&^y=nn^5O=M*zzsT0b2^v2`2K zG5R>0ZchpRp2E-*z=GqZ1B1Hwlczf;5ds7ml+TY@I*OUlYXn-Jn$=njX4b*L=HEek zj`+eE*BoaTBvwKLz1s>%!n2*BSIR<*_AV#>4eg!t(3U6-e>)`eiLW7QhQc#L0PJZR zhT90JlXVHmZXfnqdZ*N?%UkkXjoT#}Q&o4>VO^Evq67AtY}YS6-a)~b-zaS8;L{$n zKfIj^A>E6YPW7{J{;_MHL1UA{7cN>oJUoSX4D~j`>QZON3;y)hQQI}-tL3YbvUa*N zUEy!`Xr&A%JX?6;%%sct^m*Gu?#5Sf>?}*jRg7a)EBD>)IOEuZZ4K$)AYh4KZTDL(&6@j}Mobju^?zbkxb_4s`IbGC zG(y4CCoECP<@*!Q>_+S@nx=jBz;UzorcP*Oje>HR7*wB1Eg7PR%`=Zqr( z4x^RNL}ETA2bF_ZS^~Z>=ig6*V-&}+&OE_UFx%6Vc)V zO?qVR?BPdu6k>_NDP>h$Vqq$mYRWN8L}*I=Br_ks?zSdrLnEl9aqn?_=&UT8g>1y6 zD0~L0*ek7k<-b0%dfO5!W<&XNZg2nXofN=Xm4caEV)_JTHQHWfSQ5 z_=jpCf3}o&=|J0XmA+W&%(1o z=BKuL;h=iBjLNNj42-MuvKf*5*7F6zse^h1t;_-L^4KyjmvdiKA%BH$CHvoDxP#2s zzo)OXr)5d8J1N7Gm&103KV(&Lq{HExvFRk*D{*o1p7`gGNd>;=yp~7F2us2}F(0Vh ze%JO%`u+Dyw-tSFs8!QvQAZYC`DU~$o|q; z6koi!EBXB=ItDW&=OF=}ARAoH2Q^w;Z$W6gr;5`RCPw=r? z91-lD5&@t3-0x=^A<)p^IK#P9vO1L^R8ZgIs@bOkM-`ft?hcFm)gEfGZx=6Ak_cDp zhrdzWtn)i~DgVJy|5@v=XO)$#r~l*6V!kKA(Mv)t&PBwl@ zDWjqBTnRy-8my)ZP%NsQOYMzkEfF&krE^Gv|~ygL=0U_YUA%}-qC z@A=wputH{@LohDOxtoXWNshPa2~O-mh!BiN^bBgU<&mSYDMr@2uCH8C-p4qBsleeS zOI*%*unI$rk2?0qbfke5KM+W6;Q!gf%_LU>mXF)e&|qn2RBZp@sT&M*OPlM>Qt zs@{C>kf6qhm7*79KXhxMrd~hYt(>G%EG>;>woJ{Zryx3`y=pnEv3yg8f%lbGWEV*U zMHu{cOzknfrq_@Na59MfQzJeq=WD485wNH#M^|4Brk83asii@e*q$pl8 z*om%_u2{Y?o~^Cvo^j@I%6f_RtkG`W{ruGr)$XC_%Tq~Z(G!BWn;n_xkuKJwpRMG* z++7S8W{=s~z8^Kt_XaTEd#~{HKwB*_nxKmU8gVW01j*l5Q}_RzqHSH*p$Q zqW@1~a9~G}L~mNRN%BHCja(M}?z+6yObVB1?Il4`PW1?GKw%Lfrs z@fe1Er>;5%hi9k^@@Dmrg0JR*z!_ZtO9@~N9#RtS9`RNb0uv1!`lBi2jr2UW003ZT z3;Oqd#9{#bYXTKc-~|O%4;-N4TYp%$94Y|N1H}Z%@|JutAlCVpev3m2|Cf630+Ii_ z0vN#lza=BtpMyn%*SlW_cQ2xE!Uf#zS26=~Zl6!@CiTbrPL65SGXMU4xjLBaBLAA< z!J4x)Bb!s4=tO;F5G1N3k!b>QoEBXD@v&Y3=QfgU=}Cn3F3yu=mg_@o;3)S!(_87Rl!L=q_E zhae$pJ!XV+`UqSdK00;O@6_b0g{xUoG6^KV$$m-SS%D;O1(TC7T*pSGk6U-*^)g1z zquQO6ELLfa|5y61StQN>-y*+<1Sa|6F10BYfqG!2XXS#_!r7yxGf3F+jX({Mc+D;wT|G(H+%Q4 zI+J)ntfJI`=kL#IK;qi6VZB$Z-1q3Cwm?5l<&&q<#jum6zsHxq5qcj#Wa0tL{{b%_65&k{bvH!d9c9l$b6!LFn2%!&`x&3$Q^f}+xL+1Mi z;?X){>=K|OJ)L1vruY4X{$~*WC3-E!c6TRLqqW#z@;S^h!GZtKlJW8KC*Kw{_l048 z2Lyz_i#v`#z-&bC2k!aF@*q^LEi7v=j`{iw{*$@Vre_X zDdx262?U3g0NgZ?Id;fKrd1e_t>V&p=ey>Ph|Rbw^R`ybRYCJJnNvE&Dmv(?3W(Us)|{jgUGjpqe5 zw+mCDvEK{J4W^G!xNu0AS9&Rx?dvud@sp#hleG?=OH(-YjeMM!jY(I@OD9zukf@Y6 zSqgy;U9jkIDa+0Jqb}DII)!~_ni&|L3My88Qn#l&DZSRAF0L>HDZd!vN358yIAZIG zD{CsZ`WM>LDfh`()6M3NXom?V)e8|BThB?WrPfqyoBUA;1l>le>+)bwF>cBEApQ?p zd!2pZ)1z?S$PnaW=iN%(C^B^cR4Iyb4g4@FA8CGyx@Jv9C!49mOj%&eyUX?&QfgEP ztJ4oQq)2^HLfj`9aL>;rvc z$`V~|iq`9cUInhVFVfoVSQOBYYZv^S!e+U{RykUq>z&^)saIq7mI@|_k`$%^d%KrMTr zw((Wn-*tE~F|PN`Y$L*1vH8)+Vw)Fo%#wu?t$JcZ z>~x`*aap^TYyp~r11H9Mvfa76DCj2YeY2*{%j8_$39#HWS_UzF-?Z|`k}V9pX1TD) zi#MXNxgBPoAxqHhk5e_AUf26^Q;63x#dWZ0%wt9GOs0H^tN7+G?uYLXU+M!Yhrb&a14$<*KKjh2W5oVD!(Y`>A&(TgWcoV1mjZvH25Idt`1%n zj5a>2o6R)!g)6#;Dj167rxymeoKX#M(o-C!SRHg=Rn|>vf>aPRqLd^HMOF+JUvYA& zo=U0D4(CZ6ToKeZ6YF3$Nak3*`%?M%c*|Ret>)XRm^mnOVjKnm5ZbdOWb;BBS~h-L6@iemx#H_!txq9`cPUJ7FqCnI%IgLCrfO+ zq_Lyp4GU;Jb}EjHQO?H^ZQVLV@FxpOuoRMJ80*)}g+j{eqiia&w2SCbAn970#a5HbF{y#e;6Id^k0tC z$Nxo0auwyx+mqLqhfT;2vWEQ*x#!E|E8okm^C(&bt|~jY!CGK(7YFH77+i*Nhh5H* zs0N?BSRS0R9VTv-@6A{GUO6n;SYmN*CVaH=2v?3(Lk{>$LC35jCPPJBZE$WPL-I^B zTFDYFV)?%2EMBX{+IXHf7t`eX+t2vo)k~J1pZuIxbxg6=p%Ir zN_jMxkScZ&cpOSkSeuQO;scAbFt^X?zP{iJE}b8V;m-$cf2`W*MD(+x}sV>ALiaRtTL)vzA+g}Ny$x`?4*Qd!VMZk;K^d=NtXf)FbsRK~Yhf~r$f?298dXvf> zBvvH0dlTlpEt94_ddg;9(U-o`Qf!^%d|upwu|ihGEvZMqF^J=m*O2b9g4fzThF4FB zY@~ss#mh&Z9luLUQ6@uARnGoBqlMu(_c^qH!>o03ay4fT8~$dDF>6hOO$c*ZJGTj( zw^nxIrWVlBUtEe(CQ>r@s}Uv{i-Log<*9zUjE5$7eFLmX!un0IUaZ8Rt;w3EBTD`G zOCph{xg?E>I7XD%iqP_xM#mS#o`=p?FRYeqk(MQ>)!@V2cE&*;gKyijBD;u2_%7na zi>exF+S;ebTbiMqkn_vZ^fs1R24i`FinaPx>~vY@ zw2i7J%p6;3XPTz#c3ikdA0EnfTmwK{QJN~=t+4Fc<@KMNqw>~D)=bZ z|D6z2%YHSWY)$7qzo9o8j_co>u!_`N8*!(eM=9;74!_pqpk7n3oFgx1ja42B{SVhl z+Al1wxqayhdtua;p>)AoLo(g8Ro{U9SsPPWB+$FPDEP{Df~@e$krhtTOx%cCNFR)s zoX-7|T)xomEX}Ux)s^V#=d!6QXW64lQpwQ_e;dmm<#0?sMP>CR(9p?)O0-3k$5X*f zRvjwM`}AGeHF)v1N_bu_2l1^pxAL%Gp`GaK<-H-oAFd|JlHl?^iT| zo5S~F&A&MaG@bHbQ*)>`=4xZfDn@WS<*}ZQ! zj%%B7h@xX2b1acP6OpXZIP+v#xAr(jWKcd)ji_*H%`2-{A|K$Cuu92%D*yVEwAssN zTT>>Mzb%%&-#lp=wLmj+X0-Mf8zl?+nntB1ZS}# zCRe9e5Gh$2d2r>iVA=}|=}0SuSW{G*oxgga&M_0`dzoc0d`f1M`i*_kDGdMQdyXR| zfg2betS$I}mx?N8!CY7#Y~sUd6l-(*DqDeIJ&3H8Kqmqm=t?c9y3Sy;PaCl;hiE|X zU2|KleCa?NvSAitii(i`NxIhqU1+Tl}$ z$=_H2mP4%*?FafO{n#EVsM8Znwa%=gSH)zD8l&KdzeoDcg03y4fHL_S+kd$68i`T^L|=LpfT!INn;) zm)ATY#PtK#QL2S-{URy%2MsY|5!ktv=^YN-fYnZ~z;?70%HHu4gJtGn`Q`OfePZg~ zY7FJUnWP-OMPCOriDlA&#a$`w8L{i=RHN{~2G!My>v1;pwM69)KSh#T%6q{w`Sub(@f476TnO!xqsNER$uqT`xBGvUq&H9>!jcC zM~B+d(?iOz>O)kCj4bDo<-sr+xEw~6HtvwYN&8ElihD|7)Ku7-$^8F`o4edtgxN~< z;uuY`V|55_ZWOdEYAX;cn`DrjRYyxd-Kpj&x-=xGxRYq|>7+;}fQdhrULU$8l?k|> zLWJ+lO_8Z@GO+$zhfWu091|kODGgPB%D5Y0^xwEZH63qgdKAYR_^+?IB&DGTpd;nr z!UhiPt#Dy@AIuQc4MAXkmwo=8u$nd*KT2DdK)ln+a5jj#!fj&IMUQiWZ~Gr$REK47 z+=@1_)$~)g6)X;&fjFmX{3PRa4J$gRYJ$KZ=g<+t@4yv?I?_R!f(r3E@}WN<_ovqI z)%=EHx~uB$C^Y;biR)&RszlJqwCrI+UbsCQk=bd|pH|p{Nzr>VA?49%8gf%OxjGF_ zs{h(-x(VcT%RR!PuTI=(QA)Oj(M*73#PkGJ;!sKpl{^QfmrT8F_=STZQUd&?LB%Gi<3!*eY2E|` z(9$>7=?56*rwjy`eXQ;*!ZMEduUDA74_A5WO&EqV^j2?cgEw8$!wKvH!1r4Y`dZ}Be-(l~jjV3VS#Ha2S+f;l|3 zDIK^436e79Uv^4}PF^;~Nk+cj9|zHj@S3u*|K!L<1HegxkCG8r2+p?@7uh>=tUtBX zG#Qzn)m8@QZ8+DHZv#2i#M8bvdv}QBuhH67F7RAO-uUR+{h@iax zFMQ5)7=F`Z>U5-h!w}wI95Qinnfl|Sxmb(F@MJdqyj<{SoDl5(i!wZ~L1m;`6ZFYJ zy6&f>o6>Ru^h9dg(R6PmADn+-Ux}DrZ7$gfG>X$UO&c1a0~Q}0QCrn)+{H=q#x4|O zaqwR$1EYfj(dDSD}_qZn}g6jB6U&T+!S<&mcxrs!35nB%5+dWt_TpkHbueyf%ni zp@~^mMcgv@ShFm@3er^_40F4?~_>jhsCmqk7QIfcG zt3PZ0P8ODquf*7uqf8`bIjpws`!6Cjl=OYf;GsR2IyJC>WT`5{gW7co-}IT#1_8;dWMwrl zS91B(!KL)O#SBUD@{;)!1#u6byF2RZ)Tgj)zHMFJ*2*__U^7Zp%^joFv=QLa%lw3m z&Lt1)&1WB&yx8qVc$$pqeFt~MnO~cM)4Q?=y776XO^4WAW~jkrG5Ft*@N1}^`&nTP&D;1FqSZWHuD=XJLzDVK(&Xn_ z81eB-sGrH&PhBjRYK_~ppOs1@lvh;dr)DKPb+^9X3G#kSxhZ}|Ow=zXtzr7|x*g%9 zmR_S}qkb$=PtQI}9KvN8m&|k=HdVXD4E476F3bx^DI2of#ztXAR z{K8YLLVj8vUW1jLQZzNq(|cVwZr~;YI!N29&dH(p-kV4MN{}bSi!8Z9Zo_S5<~%b> z(-25s5S!k6s8rl=;Xi|CG&+s45~P5Cpzq+o>U5e^YIYUbSXb|@^o_((^kaQS?oqKP z+3vq7CzCc5>=9nvpR&nSQ()7g-4efu-6b#vqK zA$VqPdSMM|S9fD!RrT5gz0WCw5ozH$)CtU&cHh6S*noizweoU1r(PazrdG~5$bo~x zT{c|5Ptd$pb(|o^{0+1lC!NBKbKxx3RJ@FEE~b*A&u%=R-AYD?^{-;7XAkJJ`_=v6 zb>MFEvv0)B(+t$m2jC7RXVHCaG)6$9n8Z2L379#th_1qUPVL7(xOFKr&m0=Wb%n-- z9zIV^NO8;?Gfa=-mJxc8e{)>+To=cndC4lIqo`vS*T!ZqBc3KYaMH_j?>UYj+>6V%SbP=&IT&yt^4Y5#%5TNv~QOfV%}qvR=ZNgb`e8!kWdzxlGdA${5Py0O@^g+~xf2D*RsM{|=|y!lCM|YC`s&smdK7pMuTGAtTJ3>9E|(;0EXLD=(2#W$P3kUy+mIDqfFRLF^rpC1$+PK5vvzNGZyhWx$;78Agb1oG4* zU>Bx`_&z@|0vRy%9x_vV&9M#r`;83x6v+IjCke2Zd%%Ca|95-q#U5neG2fgog1^!1 zfWidWg6TO0{B|B~{?T`kBc=-oh&qn$xCFBE$~Qk|$Ksub2OtAHJ-`T5{`}AZvSbEI z?+l#YL&TB+)AvOEB`(owb1ZQXYy9M^;bQ^ecrH-9)OSvV0C>}_0N*j*0f?+e2B|)4 zhZzKL4r_N1cH_6gb+&jaA%H1_KW6hL6o3aX?DIx!8Fa!lXuH3Cy@iOqfhZ6S0B}R$ z0k(`CF8=Iv zKA8xzKBAsE8nMMA zN2q%xdw(c0Jf{y@e}RO^P$F0boyK zCehA|?e_P-GEl`FMfrkoY_o*thj#B?Ck+3n&h<-3L8ZmrIh{t~A~yq9kr`GJ1>)wA z+tok69Ex!Bt8=ia5=cEh)>j)DT=Qb*$gsKr<_f%B%ssHm~f0A)l!{VRFWPAQ6{kL6s0jqTFp3aB;B^i?qp6(xb zi~*Xwil}HPo}?qQFVer^Kj$CkBdVyMIu95|Dczj3)c1nSOGa_7W(&%pAXbGlsGrcS ztQh6?=Fr5QWct;`2)k8`9S)BG-+}te$lyJp38wnZ#C^cLX+pn-S>@p$s`6%f)#5|H zkxFlNCIIQE9W$UA=QjUG?<>kjK#t;{wf;b;3z3_)my?4M-%H8;mZwTp7{r>~ufkoy zw&T1>+iZ9|jkv9PsE`2ivH3Y$57{LvLfUuy*ErLgt$=$b=#Y0K&aZlVX)S(z=^;z> zh&b>8Am*XY*k(L1Qs#}L?qlZ0&CdrQ_k;%_2c&&X%UllKihwfA_dee43AhXJe=V8x ze?7_Q5cLf_Tr?i+CHK-WCmBv+Bb_pypKc?31IkFt+Hjb*<}WeRE7nBze?DfIqj}RB z=4S4^+WI!!eT07iFC11`%&JRV@dY|TWj|2SPop1|8PNN>Yo=oVYUH#0`r|4@?D-oc zs_}BlwE-w2+7Ev5u;A<%s{c(b(QyA&r@Q!dnTnvA`9wH=k*Sw4J@flt7{t$?Zk9+K ztha|HM$#O^K5RNmC?5)sKRtek2klF{z$ppHwh`+4LRs?lnRpo|e4Ae|YAXy(Rl=S1 z5vlC72*%Xu`yc~ye+;s##pgNG8yh01tT#EH>KKHR>tF7)XN?sP#yHWIQEeza**H8V z%7$~f(+Z}q=zju3?|_BzA#U{YfzO8 z%i_X(h_p(ze{#y^WFCBoIi{i~bq;e-z=&aoJ%Doe+-F=Xmh-KC8zFv|#t zE?4XWja5PYiV>neKgKZZZ1teVDZ{Q%IMho|=-{7Q@M$AFXN)CSHBw9NO}vQETLy{f zCa%U5&9r5MiO2^qZvqB>FVC|^2cji4i{q+{RM7Qn4u_i%}5;_csX6HfF&}A9i z7O&ahASBb=x4v5YG)hTwv}Sikf693|~kULDNfUd)l8 zLfxw^rO{9S0!zj*(r;8=@OpECS1bF~s>({k6-DJfEC0(qxMEOaFzQ>6o-mLZGBaq8D;L?^L*7Ja&_;$JOw*RH3gnfg&oZ2f2Fvt!&bM{V;gOr{p9!WRGH;@@=S#f6DcY0o(ZD-CP=RYS>fKVRJKcX`&~DH_`6+x$y3w5!*3`St?$ zRNR}|CJajNn4)c#WqM;m;_Jmp62;`lkVG=LIWVNE8>@(wo_=%rZLpIGHCHWZ1l_!y>8F2dRxU^^`8SFjRuSu+D?+_R5m_}4Pb288T$PE~~FYU~w znO`{~R_nK7N**-_jXE~E-8Tr}(_3Gn&MtdY%ps}i;9BbKdY^g~{%}faG;FLkn9dw@ zp9W&EDh{O)I??P-$gA7%qYWiCZL!T9_?Np_TiM}Z(-9MzB~zr`bmioT)POzErQFr% zNf`Jgy`&t2D?+FCQPp6(q3BILlKU}QQ`^kE2tI@S1?!DJ@vU@DwKWRjVSl3>!hK(g zE6P!qTpbj_nQ}}J1+BftBWPzhuFF}=MrGT1X6>mDczJ&MbF}%Y-tGT#*_etZ!49PN z-km5g+sMt21Xl%AZt8}zWu3QbDmzT?r6ehDj`fa>mkHjs^G46 zXkw$WX+Y57ZdmzKVSm-u7gpr7<)LX0|ie`*BEt(3`Y%7~HI=6;fuAG0Z}%4=|4v`WX!?LXOODE>6$O}U$xsE; zpN7kPa)w0sKgiYaXVN?tfUao*KDaTf`H3b#qPC81J|8DgdC^QnYH}be&A9&Sey;@e z;`sTK;#!!MkGs|L%Hgpl7|v(+)PF&Ej=eZUsv#8JZbM0eYzzfA&OO13I9Vyor-Bhi z)1CSHJC>R4w5vRK{*j?NzWlK;L(L%A%@v7-Kt2yM%ksJC2*nRk_|m+HGuCwT*bE{u zvu*cMHPq|#)tx^n&+AE%rmgK*BMB8@UVRReymgH*6mr&A<&cs3@Ka*GT>q}j_x#tF z$GQc6+dn=)!8fLXW%Smtt%+76QsIKj7lfNI&h#oT`;TaMzVwPKtzm0!lwf9Mc1 zvGu{LVS`;0XAxg}kI^l3#eTpVvSwNcBbra?u5ItSa>GC4^hWF6Rc;PrTIhQf=k9+-llS#8~Xw`dBNr85x>j z9KSm&CKoWAXpE=TqwSxWh}i!3@@{dNffh0F4PK(8z1ILAk-qv3f>XwRV&t*s z=l!`f4xIPTYQDx&LN_q6#Hgyl1!MeTwX6y99p!Tf4TcCQuB;k=VTq272)}pDj|J^8 zKmL8XxFvmkssD1e=>W@IQk7>z!MM2Fx?sSjE)6m}W>TEJ(vLC3k!W?}Qu83vA$e~F zW7hEg=~wDyr!gzzv>Ts@F?>0#DVLqIe6YOR&r|>31A%k%D-OvUTQskM}))MjUOu3|H}Uh!T^(GZ){qB5;uprOtBcz|NW<$1%=Ft>EU< zm!UeRdRig#O%oKw<5RTK&NEIm&~IqM-k3Uua5-p_zsDO|P69R%g5CDOY+v2yZgwSq z+3`!8LB73`E>+jAFafO*g6MLkr^A&=HH(=ObBmScoh6*WC!5FRI(B63sSB`I)DvAC z!X9%I?&#kfzGC7v0sv%_rNx9*=X4Q8zd7Pi_VoPmZUhS!;x~%ye#HOIo*JUx=vf{7 z37U9MF!CdJv76jjq*a{>pT=BMs;k@7P~`WfTqF_P2N`yF=`#CZl6acHZ|D#J(?bAU z{BFqgSgUF`DkbF)*l^z8s+|ondhQt^PZI_!a_^@Tg))(mE!aahqmOge7_aSvrAs4Q z!LNpjA0m*qyN*hL%|SaIbM1nEaceQ(NlVlUxXZJj8y4==283P+QUS+s~Y z)V8x!>r};$b;i(AIl{!8=dDoxG0`khcQ1l^M@)!0P_4sE+ueFJGG8)w&x=CZh&SOdmB6m z89<3=E;(V}8$v!|wb&q_oD-Ow`V+?vmVE4WjSO+{^Pd@Y4*UHi^J5(qH#kbaXkKwC z_GSAu)(Z*OSfkCw}0lSBltmRQ5=?8bNq4JUI|`Q{{YDFDVIWM z#Zs>uGlvKiGKM?GBcq#E($UM!$X}d@J=>ZeYnU(}Oo_b?aj%GJ{j#%6z4(u^IFWqk zL-tV0p_6(EvXecK2Dh~*-^pG~j58EOzCqh+H~tuHvPS0nol<$q6M^LsFQ-Av6Lf4q_`xMu#TT4 zLKqbaRSY*GGQQ4o7)&~Aq8DF!As*haS|&?m0Ubnf1xc%;Cn&kn=&vBfPneI3)58_o9gh%_}=7n4AV zg-w_$@eV}#4sEqam8fPOTANZ8Cd&*^z5g+S-(L_$>6ESS3|{(3OH)lnQ&AicE3FnW z_rP%U>kmP$-2cpXA)rfk=FaI0aq(*dL+NCXj2uKjXsrXwo;WV0js~3A4Aa519XVT86rp7K~`-!IiV%b{b@#Y1VmZ0_MGDnF?Jq9UUqw{J&I z$NFS}15yoQUnG#N%y2E~{b%MKx8VYg^`{9{JgVF2hn**SGpE-=Pmq>URYN24k{Q=f zP^W}I(vDC4OSs#0gY<)8`yiLG16qf7^3jhd6$7qSmK5LIUa%yOv0(Nn=UTk4)&F?? zMvOmKtGdgzsun5=Y+zGN7m?8S;MDbiBEvFf?`O%QO(_qWQqcTb{aHctCNNVqRO+wM ztXx6}E00s3Q3!3HSu_wha)8{V{eQ|uO60 z8^#~|GjF`ZzRwDRR%Rp|W++ed^N!1K0psD*Nd3}z@U8n=+!Lm13!lwGI9piCkPt=b zy(0ACEf5FThqqG6JDgdyY7NWWvoFJ@8oo)Vl|j}o1TtJ_^W5}OJqR>fO{m%&8zuoA zGLcoRnisweIQsp(U0juNvsX^G6Q80pWY#74yyCzW*&NZdMCg`x0wUh@RQLRW)`p4t zjVIXIwwzyy^p}*Y8Ch9+16$5inyT2Tb;~hyt^-#1;E-l9!l*At^3&;@X-K0$`^ZWm z9VKRt_YaxKi@3w$cg*^7Gy_vnUZX&fS!2ReNO18BNy3txam$}d&Xy#P7r2U-qH%do zvB5)g4!=a1Fmbz2@GMY@rQyX-idyPHq5>xMfK#WR99N6y3g(BOx1I>ZlL}$`oVBp9 z*iu7794H}G=r3G6C}?@b7hh_XXYE#){1AX!jni ztmo2Dh<=2$+B;YhpZNvfC>5)*p1@$^Gxc&!pF0GHOp>U!9|nTV6hyzk_L##muCZw$ zt1nuG#EpeP=6?`6ZgPQvOkZP<0@}Dl!7^qE(>Me0l1QKsAOkgW<5?;s911eh|H7Y` zYc-DiT)3=AWT(dr)U%ni@FXKhshfK4fA?powG1>?JVcp3)RJe{_2F~!W2vGJmtv#O zrtUt$XgRcix(lmY%y2THCFJlEPwK5N;6N#^6WTJ9P!UF@az3^AqK$Em-H{RV&IxaJ zX)~rNVLc_f3v17L11PqZjoS(vido}~v$>m#>nzJxk1jv{2K=9F?E* zElj&{Zm!q3mMemm7KkNQ&vXD2QXP@+akvqb6$e#J=Wms)fjV=rs@_*31Nfw#y!BWa_AD98f$XAn(3p(4w&8M_`0O9Jr5kgjMda6%ntLQVk@1pQRum@uyWH#ee_QKEfqm zoI(}h)I!+0J}`%oA#(!dH|4$^$mpTApU}*-df;AmT5ZrB9WhFs1m*9qk=&!Ej`plJwR&q4blh6 zUnPId76H>u$qLb}exs3wpt##lNOL;BbMcY|vH81J22a7tfxh|W z2bo!i6SCQR@euX%Qq(iFXr(Hg&gY+T^{TZmj}NpIPvcdhDHrZHP|Rl)T|^&orrj+u zX9bIQ#wHpgSJJH%vo=V-c}_bbP*y2r+pEEFZi@->p|e4s37+v7IqZxdjz(kP!aTY$hXuVsy~$DuZBD_3OsllDTuV7VY(f5Kw<+R4;sC*7 z%xU1R6T^EELTDuaL-MJKl@8aOLFux<(Y0Qvby`>~!7h`2k)pahmt(J;46!mQ3Gaoy zlp`CJx0zq^(-a{co%7SCcDlRm)RJlUYQ0Vpg(@04P-9-^NUajl0AI(SUkdXQ74Df_ zpb*G1CbD+i!dScruLNF3h>)HX4MQhN?aDMuOxQ(k6QdoczWTzf56Aa28*$EYx$fh5>GUvYYu8Aj4P7?^GqhvDKS)0&c{>W zN7*2nQAbT;kl`D?;Y>H7yKgXA(M4uOiam>eCM&`2MzX_IhZ0k3tMHwAZb=)nvu(7% zVT#GylJmAb%7aua%Wrv!b}rLN*KyWpK9=)UyCX8a3QX7!ItfNK#P4> znS^)&`O0}MhCpQ6Dm$~Txk#iR>QjC+0pG@gUeYut2a1od$1QY!Vg#63&p4%zV1a1w zuC_9NcVwW|8ZJ=0UyIJ%H7CrbSGXq|J`3|JrXi@UJ|vI|=!pG|{{`mLljHg>G$251 zs5x-^LUN4+!k1-u07-o1p-BU7I;j5H@QK^6NS2)eUZk z>|SbNou^>GZtV2G?^>_NgAiO0Mhu{@Ah>XGkEh%_{FZ|`a&rYjux%nib~tt5JHPw$ zOu7aIDcG(oCoNBMzx_+#5FDTd!U_f~23@>v#X*X-bw_1ZqGmxE(_5wJ_77D-ix7?*7!hs=?z(17Xd!%%I}3f_yVETAw?T9?IHuZo`attMS0?7HU~hCYMWxlsD8f% zp#}l_Gu?o#AE@Vu5L)mIe-n1&E^ury4Da3VV91MI;vqbtzT+Du`7ix{j0gOGN^17p z{eIiEHhj5QE%b!xNNmUN?|(u1W-CMTO&6%1W>iwr?bVAMy{Cxp8?aq=V%61n=>KlR z;Q^)&*8Oa5JUq{GV0~93^bMFv-uCD9K=7J7sq?fDb#b}Vjp&M39xcSo`mW}ptxI13 z7Lp+VM;Guphx=Ck8Sjg`{xdU_y^)(qx=gSQ-oLGUJvI=fuDx_NwYP!e&3~U)Ps+Pr zH+!vrHqc>WzP!WXe`UO}z1Lofo7(f$ zuc$8{xNFj%=!Dd_S8sy6|GWomCGwC8-hJtUapZ>J!f#`?`Ii0&SlSghUkMxgZnFw+ z?kT$;1z<>v#sLvY6H>*ly<|5xE}vcnE9@&kR*q-?>2gmrdi6$@zG2jFW4f99>_*QN zZ*w0N`n0|3pP@d59rL^eihnA5T%Y}4&3$E5UCR>Y0fJj_3vK~|1a}Q?!9BPIcXxLW zZo%E1;O_43?r?DCaNoP{-B~lU=EwY*^J}lwr@N%9s(bIRtD5HxuyHJM^Y|AR`h^+S zAmi8hOO>Q_fb&aR5ZfS&_Ktk~8U-3x#1qu_5tPb>(+B{0H{iQK#?^ZVJrDe|w}*#~ z)$?sc4>R&@aBItYZ?EnbXTZzAwz^mwutw7P`LG}WfKb3L{{0Uf z+p-gP+ADE-yplP8Fi<3?g3YCn!@P!b+M&T-xm4cmDUk+Za2|H~&T^EBQyfZPqsQNzg~!~e z>;}HcsDphZufo~RNxG7z%E_nq$ioX}Tj38w{LHDhN0N;5LrgH9`9l46c4TqaSB1Uw zz1X=dTI#q_ITAEOV)E}wT#5arNK6b$WStd6QgcJ(Yhr9|raZzYe2r-z@XS|#qVhI8 zC4B2A-|=B=;_K&Cv+%Ah2nrRbztG4&HYgMV9q}+If#3`mIx+($v^c6tiuCRsa0A&3 zD#VY{w{+}e@k>)V!*OAJdT1hde#3fFRaEoGuOVPVEGo=r;fhg(wP%5EnTkj$Tj8nd zX9P299$T3QVqyo~C7n{<8N|LB)%58#Dk3>0p(q=`v3LcDYxKvb*>9n0nk;zPeqF;N z=w-l)IA30?{CM8+t)E}XrM9`Oks`Qmr?UT#IvRyIT&BCcq%-`ozdJ-JwoFK_Zbt&B zqV?S8p^?cZ%Fvso6kLC2#JD$!X=L}vIx%yErhI0vE85m|RSi^pKtnXn zB;Tdn;&))_Xu+cEF5TM=w~M@dkl}p7Tdc`t0~t^CV(R20*hep$ zJ6EPj3E8Zlo0qPqd`4d*Bz9hFj?W9!M5m|F z66ZmdnJA!9Jz9rrypH7PoR+HDew~EZi|JV%xtyp=QlK$2pJs$USp2i5Zl&7#aap1)@1YI(I!9~+w~|L3jw zji{nF1Y!SitI@hrOJhv+4XdQF51j{FPh^7tyae(ibvaHY#_NOaL-*=Kyx;w!ANHNyqqd)T&|%kn@*Y4)Ofh*;G>j#6l0e`jn+##LPoB}eMy)7a>zwF zgc%%{vrLcm8!r+Z=$&^X_gR>ns34WbTofXmVecG4(Y#ot^tz+Brk`O#(TX4&da#;y zGYRS5wjpjJX>w2gNw}WwX({tWQdBjyk9}rccdfGH#HNmz1_|D7;kF)&ndEK#?Fo4P z!iV+x3amPLr~#o@>)X(5zi-ucKTKyJCPu8(s7#v6_|bS_6|jdIBvE75ENT{ViL^|{ zb2WNoA9{~@4%*BI*D7SeP5->8<8s4dn{m{xw&$I2xaEh_r}q!Zog$%fKJ`wHSh3;V zXOz~_r($N$Q^=K9tm+vZb1~U^XfDpHnjlZ9A%W-8!|B?{nXdNytBy+xRlQqL*LM*=ib?`akj1Ep^oXfqHcOn(1vm zu9wa`YP%>mA{UMXrqQ+|Nttt+zR}mv;TnuBg%mLj+}M-u#n-DCtGcgqT?J8^v%s$m zk$APw+T!hGaT&W5V@*)}zMtYsC>~8=Y2cS~y1oQ!lGqa^ zd_V9l?(Ovj=;;OC5uaU^lA0qHmE`PKzH?jU_tfEZ(wA+-Ad% z=2y4O_glQbr-0iE%g%fy-{4U5LQD*Phg;X>f$bB-+|gZuwhkO_pCqdf{QP-aQZ$U8iQ+s?{YCl^|`HhHUBc zG|)G#L@q`q>V3!!kVcC^qP?6HtD>D*RB&)iiVZ(6{AUweY$VIc-4?Sl%?Eu z=@}C_C7u8xl?WF)R&-_-O8Y$j^yib;AMz2p3Yh#)+F8o66f!@E@f_K-`kTUd^r~Ua z6m_uj0KL@AAwatW=`1Dx+x2T^Ukba5>7tHPmaueSI%_nHnj7h zoC#FcJRv(}V0qfUg5;$K^z(gCUGO}A8^o^wlcvH|Ng@tmEjo?jcEn-gq~Mc0Ux8)T zAmMe3#N<0tUs`p9_kfFS*{AMNdGXg8*$6;3Pm!5rg&JqRZbVW8DNdO!Zkmq)dQ?R> zG$}KKgDNFBJo?~aYs|cn8*Cm-$HW5W(81rVp-}RxB}-TiMdGkq=GUI5Ua9$CH7`w8 z=u7O~bEo%CchN_;#NiT`R)Dpjd`#(X4TvtS>QC*ZRHt#7y_OnhrWc9^hwwsiQ)k)Z zemf`@-BiDuSPf4(!;R8lWUnNY9e&Wd7QxS$SIzF6*K{hMzwU#eM!yUu3+I zL)wACstry6LK$MV=Y5yfT&Izw6||BtO9U@H!|v(CFR2iOqzc3~Ev+O*QU?d-wr6mL zvqJC)4vXJOc{Go6;{$6~OSpu&%(cT8&cYTO5)5gFb6*W#Su2Rm!f4WDg{I19^I2y^ z#%!9ZIU|jjR}7m--q}HzryDmdzJyHxRgn47g@6_gbux0HTY$Lwv7GbxGx_oc!oF!X zjZ&Q0n$UIwjVS}iFXB`0S98ezVnkkqg9S!ks;`)Fu=h7_4p`ovq!uP8rq@lz7yMr4 zl;N;KC6pP$+S`63ZC40Sm~1VyuAOX#G(+N&1ad0unSr@leC=x6PJ%|GJnmv~c;jn}*~@tV2o*eN(02rn&1;r%j*}8e z4$@WfUmWByEOxoF3-K(Tp!5yb82pRqR8>ld3sEH!=O(fh#!*HUzAI_29E_Mtxx6$o2M6`P?C=m@7EFhJwXBB4bA@G+ za=ujrnj9NerA~Zse7s*`ve8>zz%7xQG2a2o@tjaMiya6kq{^foAhM8%%ydQ(AA{tS@5l6NFLx05pjr9iADCy#;NO^){yAsA49}sAO_q=J& z^|H(6vXE|{kQG3u&>I@*emvu+vygu0KjdMO;2&r_G!3c|R8`RoQ{+I23!&AWxmtqp zQaW;AJ{oc$Un59*e`zO(N%KQG!h@;C` zT<3j{IIC_>0#Pq>sM2PMNg%S&QZ9NKzM3f2VjzE`V~M7G?jr?qO=3ApSQb17zG7*O z0#}(dWz_(e1$dF`iv*&SF6?bBa$1VVBKbgB&BI>GE33=O>YV0FVeSa^W*rM}WBwFf zGU}45`U%IfX3TyF{a=#=9bk8Y+D-#vY17rVii`*1MVSt}yKeZUzq;q%KZSU^jvoA& z^ikl7VZnqv(Zs(I2_RIBm~DNOqSwoYix*-qTmaQvt0dSP2-SAmK!NUFFxn|<5yZ~G z31X-&{G|B?yd%s;@etR`LIsflQXEa^FK0iF*H$o1d{$->zO}|&F@ojThufcyt~2wo zRHU7mx?_D#EAaapK_P-=?>!S!0C{{T0vqj47d$^rA9o{H@!j{N z8@aIM@6L;OA-LN#%4*elQu5K#^K4sg6bt>XR2vAV?WJrrSPY z!n}^L!lji?n`9*p46@AncVzx;L`ib9$17rL{MulwjrpT)Vt zMsi!Pz&B`TutIE>oE$S$4R>%Rh}|;OC*h`(q)~E4Ybl($v*h*?CytPI?E|h&8}(FL zTsuuB-o2f&+k;&z<_d1H611Lw$n>%3(EVm4IRIVqA!vuLT=Yh;gp?UUH{2CuNU5w7 zh*tCxxB>|ouLZ6qX-o)30vMTd9y4DGswVCVVRKdWa*yq&P1YB66$|jY95I_FtenA2 zAc{!7{-D1pk zn|Gb7VYs;Yg{5-G8J}f-D)?s@z= zlr56MnvR@!P!>hN+X=Hc0q7HlgFC9ldjH$Zw`Bqjgc?@nY34O1o4EeCJdEQi(X4b~ z;(N(Al*(1s&2ZB+wEE_h{@y7Q9Vq6SW8~H?T=qH|+-}tdp`c0r)RiB65jFvL%1)n4 z-c=aTvQRYhXb%by5zfFvr@t0CtVq+WaY##4ba3@qE+l$(*ib!oSUTj!XPOlG*dSu5 zoPu*Pjb^LR1taIQ;2U2-{nI=ac_DmK@%m3>ex34kxU}70=2!6YVNs$?UV{h)ouq07 zdF05J4r1X!I9lV{!q2_@O&^NL)@4o-b22RiM>C^+Htd4aeQ(6-?698mhGOfAcbnmf)#!0yENj@DR!XBvj*Y;o&Vct@s5>D5zaK*rhw=!e{;*b+JY12=K z_|(x1S>6=?aCoyAh|wWjoiVIKk6VbBnn596sY^DPlJgof2#IPjDRQg6+pNAa7Sogr zsVI0HL&>e6TA9F72Ca7wLdG=BZuV?ak(uQhO*!R`H@Xju`J;8u-ZJ^p!zal+Dr$B8 ze=Uv=`>~|aInb=f#9epLXo;szabuFd`>@KHx(OSdNs3+7Sm2&mBjwP$7e}nT;=?mdtSTUGmg;I zwPO^mlX@q_;)4Nk%2#&o6M9EVxrN-!IT;3vXik4imDD+RF%^HCS^d$aqdASy4Nye1 z-Yt)CJnmXJA9g@ZiLpi83#E|lAm z`V(nr+5+LGvySO4_uwBLHq_vY5CE;$2K+_0Pk>Rn%dpZsK}9aFX+avg-I z$gj`8Ew@_T#q#!s&0BOxPq`#A9oKoyzmK}}TLUBfEp;|-LzV^QG9;7Yu64wk{PIv0 zll!Pclp~Fm!tgIoN~lG_(e1Z z=o)QUsB0xK{Oxf}bX7neg+k%W)N)Wf-@@4KQ6QrCLVOy*~%w z7yO1n-v#PareQ~+B8b)j5e!m+K<+~M%GsE7ABYEj*U~EXXG@y851Ss0ShSc_qCc5} zN*({QH5G_+X1t?3PAuG_vx1PnTw`!^202g_GvdfmI;_QkF{PHe4mW73#p!o(4iRY; zNda+0IXExTA|7?*e9T-SB&(lSJ*n^FHeo3X4Fy`iZmeagc^ad7gnkJ99{{ucTLZ0j z7Y&B=xCdp6)$ag~>_g;>k$#}G$?$UrN$0JU^Ot+0Tj6m^r6nMThe~r&#<4Id;LLk} z_?FAM0)$Cai z?REgYtTo;>Bz93z8xZAQvI&xQ@LsYt73fK&5#BkEY_0v?jJOuKA0R0{>9L3AUarjH05UbY>k9W;i61C5^?*TyE{Pp|)vf}1n zoc(`Z74-kY<4P2$RvG1A=qvSw>4nGJ!yhkDY9nCB8=fC*t*v2WyrSLlSh8))j9%L{ zszsJ^EIveIF<=b@;qCi^{_?}QVS|ev@O%-`_+q^!mHa++fDZBv@bBJOYqa-m@ZK+M zuTE!tsq6|A?=udthHV6(Zv;5Ox1E4*yxzTGX0(ncQztY>L_jqqXgPxr-2V<-LI-#r zIdq1WT-%nip&rF$@?9A!2Oi*qhC%*&lmqY%*%c%W8{IJt_A4Y~iAN^vxS@EWcLxD9 zf{qTrU39fTYTJQDw=o?VosDNYG3+3YDV8E~Q3?EuA5eMMfAO;m-VwDNg!qXLkgpE% z>3_1i15V`r_Eu@r4cVbc^A|`=2g6=iB&o$MbN2)0u^yJhw~wik?-m|{b?%5WZ247UHlZZsB{{$rSh9Zcni6V z%(P5kE1 z{IgLu8o~umQGvPSH?>P&E?lhhi%9EQMfguyuKea8-;9xd(Vh3r9dTqR&*3(gEbuMVA)dvF8X0FoSU1MXU&?+DlV%a z9CxYJ$*+7NKH+XjN%jgX# zOZj9%(A?ERF=TrO8RD$iOn?FI4i`nOJC68<=-u1CmSwM{Hz^y<>&TbyrR#C#bIo|jc%On74qPd zh}onNjIW=!9k*fIU82$5wDUx@TBuy>hoE0Xt6pz_W{!jULe=eG3y}B@!k2r~j+%aa z!Qb#*CK%THpxkgB(@r8A$MHGA=vdlOms~LBY*Tkb;pP^;`d+%C4SI+2 zskniYA)})`%adnG=ylANzn4geNshEWogrxIli@se`%x{gid>K=k$X1TcC_@<;+J@2 zLv)8QqG~FnFiK^`ILA*or4GiNg+;5g$ub7(Lb*9JXq9Mm7D%vnjjLa;nDc06K7CiJ ztK!leAqT(KS9yWqqmux0zDy0CfP`&Oc>h0iud>E}Ck#jW_tD z&J4;(S#e{EqP~rSt)644@{SAP&G#hV%32v!Khol6=p;O5RfHp{VHn><);zmkru;t0 zmU6c<27-oMa|0rqC)weOO5_nzccmUw#ewl7)bheX=Os*JgmX#+1Zkf*?lFqq>%z{k zE}?44FiwbX>1)|Vl^`PekpqzIaPT38$q->Uj9DGHMT8c)+Y^cu+naE2EE#Hn!8PF= zwV6msStyH?#wGYQS#95_>p=r2qN(iuG8>l7!>5jfxE#8Ei;PxqSm=e~f~xQFL4@gv zcLzLrI_&H+$$$_ys zE#pt_`Wo6e-*`Fi7P*sECuP2Az1g6omgV+~Hr%-(dzG@u<`OLa%rwto7yC7#7hM^# zdc>5jIcUO}NlspkuPoU$se$5}p+=P{n5}(apJUMicm|HY)yuvC$Gw)3v0h)?o(9+7 z&f42}EDPXV`$-E*1fl*&9dg7mPwA6I$$z-P)sg6c4mCsnkP<|yuTC=sL08$UE_A1! zVw=yX-qp`A|FeF!L%Yj@Cr9$QJ?f5D|r6q0uXP1QX?3gK>#Kh%oqO_)_( zDp>SjqGbJ778X^-zWJ|skwx7)GK`e#=!33;i32Qk>0Vka?_E%-R-U)@_NX`K709uu zGi2?`BAoQ?m{~<#qgu4&OLjzJwRkfAVyjMD6Eq_MewR{_`|-Mt_|U~Hk(j9t|Kc*S z&pf?<=0C*9O0BGiV3heo4rP~-XvoVP^ej33rfuE`AW700U|1I!gtBlK*Nu&+1`~ni z^g(qSGZIhx3}5;yw+bbnqGqZ?U#y_K8Nh7?cu#tLH3r-FS|w=vp}@4W9W$?xP1_uU zU@^vT{aM!g`=&23bOmEUkS|2vhI_InUd=YoM$VE1#<=3r2-!>qBLh85tgX5Mg>vo3 z#LtOVGXpVl&md-eyXo1JD6B}+4B8^}khB6Pj1YfA^nJ2TlcIkJq)ZcsHt!X$gY!#Q zGud%3*--+8(M=C}0Bjj%RR8m@Vt=UmJ!>C874##SdvGqno@$5a<|?1)&nfN!bU+ZVs_A8W7?5c_?(1&${S zZ!X@$P^2tjDsT5$H;>GLVsn(qS2la5Vp8RWbmO`L@B+$6<@3CP>d~OZj~a>l#DQPI ztJ1~43kSpIgB+dzH4ir1ZnUF6Y%UE&>8J75NDp2HBjY?&jQgm&s&whVr$FK0biGbC z1ip3-phE*mD|;=grp~r+v|-w}LOPX<9iK8a`FH~wa#4!#IT(-!;dEBbbreh-slaH` z1D^QqY2Q#Tu0?zK;M;gEl_mR0f%Y(_eG>FZ4ZOa5^W5BeLYekESRXvcEXrs}rQ3Mw(b^{h{tWtw zoE7fK7K-uDZtPORPCcRyjZ7?&VzQ~x4HYxD^K2%dmF-d)zMs@hFWrvRd{~*%F2-92 z>;5z(NPfb2=p)ZO;^n6CKqX^$8JJBnPJj%8JW!535I#A-T&k8_l!lON-zVPYoAF#5 z+QDQ1eY|Qhyt$@Le$#-6fYuPdj7igTyleC-H*Eb@u4@1;ljODn_RTdu9|Y))gy>V$ zjT$~nz`n1|v0V&L74*;h9)!V4Vl8rd>bVs3m^L*wlnRjHy9mHF_;*)*JZ2gGreO=E zV{{UJm|0{+IYH`q94-L2*WA&Ev1uN0rD>3(pf#F6n@FfcdB|f95>$dlG3n6g8FYe$ zo2?CC0%a0hG>gS!lGcAg?LbmdCz>pJ;mAutxV4|sHIMItxy!vqhn?blNVPW#Z`;^g zpc;ImX7rnu10tH_JJELVSg8l4PAB9Mk|1(iiT{<02#I#dnInW63HDN~odJg`tT^O; zD{%`McfcdDr32VUgb1DLqvGvN+)ug+ZJZ&)AHFRy==#Sexo;SXluDgPew=u;-%d?_ z?1(B5L*bww$O%cGl&QPk;jM=m0YPKmjE|Nt3&c1#B%ISZx#&S}JG;S^R99d2L)hfORM>O2`;2os`8@v5kn?Y}fL*WFJ-H!iGK`Z0ZytC%7Wq&&D@(VxdO9VlbW zu@)L_&Vg5{2)X_n3Q^&rxT&m%YE&bV`RO>)=OQkfkR4x99Ho@C9JtS3vSJ$FY0=|Q zV@~JprkRB@s36CC9Q9t*KK@%ky^rKY<#hiiLd4upmRCPK<9tp?8)r%Hlnu~Cv}?J# zS`C(xCvPySF6o;AFmt>5?~`bA4js{B2u>nIAKVI<&NWOo8@ksFImNZSW%(~HmZG+` zf&aZs#N6Okf03GD@RVk8W~s*L*RI7A#7TBEV)hU^dIr6V{<=FJm8yh8P9aEy4Yt49 zr8^Ykny2-JkZVQyVV%E4CI3wZLGb=~p58`n2;ubqB-6i@2ieI5J6;nZ9di?nzDGVi z)=Cd2cwEsAfGNkgUy9SgIG3BMMQ?=K_ccAXOZFC{)IN)i6bljhfDHcOq-7%k?cF-` zqGNfoS{cq=90)Z&81;>@|7N;9y;snDD%^26Itf3i+pyhX#9KEY;<>0+EmdEf5YMp_ zIA%&|BQ0Fz`x9_ICa-s6$Dk@<6BI%CzU&6%B1v-#hYo}YWGF(Agg;8e z{`sG5u(vKs;zt3Unn49*XG~jixikTzW}^-f#KjS=l0Nl3 zqa@*QfjoPr0ZpvaD*d_;T|~*aS4r@J8zM%~P^GA4l|N9fwI0EDF*Gg(4fiKLj%+pr z>%!|I%fO$A)L%LY$6z|5*1BZ3Bo2%!up8H(xgKm26>~F=k*<7>;|l!GMSpVrYvxFR zLaOc26J1XWBn(WBeJJUm02VFi%$_^s)-|lS2WK&7cMhxu#V<&RWdDsLv^oP;nXVd} zU0;Zv<&D{K*fqZ951o~thVM5hO8m1WaT6hkM+f}lDf#{3_dLjz_lLFQV;D+>K2UNR zIN1ge-pxq8i>i(8zg~q(Q1DNxvA*kE+j2N=)YqKT0dS@wKYrZwiHqBH@*9!Z8wi&q zBgV|&Ws`?E=8r3z^}lwKi@iwMgdn~q@F4&@1T#aw-< ztp~OqITI5CienGeL|qVuEe21CTWeliJ?bL%~K3wR#q)SB4eHd?wtN#dNRp`%WngN%?R}pkpl` zTp63o^w<|XzmR8FX~h=Jq;ov4#9ODAO)oyVTZKHPMDF­ml+D=R+taKLSXsz@g$ zc-y&+;wwh5i{McJL6zP*k`LyNw|&&uSmDunoh~+u4vIZjNCKR)f$9vV*OaK*$g5RPl?qg;Nj9Y%UK)C_gvbY^ywCMC@vwl!og15YF~wBxik9}h zKv5pq{%7FpQ4q6=52rL7TMn(b6km|}+S&_7CFUEpdIgdjZKFV}Ks~7=H#YAfGDy~( zvmeLvt&@Djc_uh22|9m5YZ^<>0wV{nJJbd>g9Z0nzuyD&nMTg=W-OA|hqZ2Jh^9uY zhycKP;0$JuLT+%Y^he&lP#g5Kf7o+4`W6~bA@i|x=e#p&EETjY2}?qb#cg|^hk2W>DV=Qdu&!`3 z4V*2K_nXOx%keywv+Yce>FazNm;IsUn=3Q880CWhoGdYm`fdQc0+{l9 z8_F0qe{Y2bB|7^Ttq)GW*>Uf1PQr~ay6JjEZDGZGwD@SUN6K(l?)VT_o8(mV$IB#g z@5-7qcdX^TsHjZ3d_6?e_j`WcYh_Z)V|?>zZdgrv6>;>ajati=FDcB_*2Y3JUi@^n z(^;68!6f0wsdsVr(4VcNSe20V`A6iiV~BGodJGV@dXgi1$(pUC(D2{;q%m@cnyknH zc-Jl0lp7&s6GNHrO;f<{pxfgLb{rv^CJLP%7B7i%4iKGk;A6)k3`dv@_yW$`8zVQ( z!)7BWe4kbfFy?*oeE#$Wzu^DEvCJ|0;kw;V&1@pSlQbZR0Nta9xiMSa{!xp}p=K!G3Eo zU3LSSABJb38TXA!QoZ zG7@x>(JRPxO;A26Zkc?pAj|Fv2zUJaUD>1aU$NQ4cg8;7^CPev1OIQ6O^zqIl*92V zu42K|wd={RH}p9VPf(amMc5JCxLStZ>!oCvrV7VQ~aLZ0X-1+8USnw}w9BF9krdEnfGw^>4?i1BKb-Ecr z2N^5fH?+H)LJBWR%WSudYO`%cjcrMpf0#R1}uwHGKn{3p>MwHRqE3fe< zM8OSo|DZto*Thds&|0(Ck#pFiA7!SUG*qzT3vP{y;d8;K(8G{cZ@;&Jd+wZbu>i0S z+p`OJ5H%@}e_3Uf7y5WS3*AR#bPNGc49OjC4MMOnUXET@4?$oDgWV{W?4dv)eWPso zAq40pNM>$mSsT!S_?{ih7U;nt<3h6mIRmY&07wFpnS;w{Cn9`LU)5Mh>?(Bve}n2` zTfuji2GfCJSEB({(sc)6D;rw6>*1g(Rpy+nK8_bGklr{O24x^*6}Lg7F-&F(a0e1td(X8cs1jcbK&+zbN@sO?JH>w;|&7r zO8=IvLAV*r;19v>J-jn84Mt@1i4P!W4fdV|x$_PvpG)pH>|;Hj4|uL%w7MXDtGRd~ z^k(=+ePnEmP_{Q-X2e04dPbeY~ z0QVm?J&``+-?+D3I)SR|0a}y520&Qfe{_o?_2%bY@q$O|Xxu(W4VVI5pZT|7?nri) zVA`)9b0*YnFA&i%|Dbj)_CO=x+{8Vh4Y*h(^fv#8ID`?qz=}&q<$R|CUn{@}_aW%)#+H55;+yTo5iUPEIa99v*Ip0H1&WKOaB8ppck| zppd8#Kfj2Sh^V-Pq@<*PF!YFSzGNr~T&fWU`$IJtPaxOgQ5`2{8Z&tks=L?M6; zU;_r71UN)NU{Mge3y?j8wH~joV!@_SzL`Fs5yZ<0QA@N~S`jd>ztn8=H z@P$RiFH1_x%3syi)e{;To0{JeJG;7jdO!5_kBp9uPfSitlPQZ!%U@Pj*S@aPzyJ8T z&Ddf7+WpN10>J;qI^6#a_CL5p54kuvIl-Jfzqvph5r+#b%E=|K!7XNJ%i|Fuep2%; zM8YWTMa_F&1uZ+8#_d{T<%C`$C-X#XPn-+{&bzsUXz?7z9lfB+bDI6SZ@fB+64 zLxiJ@vbwF^$^3l}=5!(E#E5+Ufmi#u+73Tlb1vbT&b6THNXVnK<{`^FAVu(3Qb;LO zrA0APG`Upi$m@n^1MKT;<|8`Hi0Mpe)~<|R?hHC|SwKVWSCx`nW{NiJ)St?q0CPDG z(=J!7gCdq3le}m|p3Rqe=m|d*^X5c{5*25j%^FtnHfWJ zY}>My2Ig=xm>#{*ySd@rn2#9Nef4AJjiWW2cK!Z+rMAVXf%I4^ZV49?1vPA_p%EVL zsu?jxd_P(S>TB=xQ<6A%1~x;6 z+;x`of#qDst89R8rpIW`mkr!UqHY{DhIK-+Z|cW!h*@E9m?bfMp3`jk8o|~zi7NB+ z?75T8_Ug<$cn8&rj_4dFjIm&>ktF%7ls(J1%L=o`$8`1uqYH>zGc#tBO7Sz@w;SFl z&<`pL`+Xinz1O^Gp2q!8IVR#A=j32E(MoO2Xqj7j8bIk6Md`U?GyCfoUetmx6N{xGvQr5ypGaF1rp&WW1xu_HR81}7l0ZD3=W(+oa zF`Y|@!hInX?^5qgduAE#KCjXz=AQ8=B~^PZd=9=>e%dir*+n>IV+!G0a9=l~i)UQB zKxDcXTR0fjHwbox4Rf+x6u5xB5Oc ziHBbIu-i*2m;3gdD*)@R= zs&RjPA%g*Z7#K2MRRg}>fQ!xd!5t&A(h_@+e}Yd0tRTf> zK1>{QXiv{#$>W_-t&O!FWmDZcIIj>f(fSgz+sIDsAFZ})9Q-vNR31G) zSKx}Ox0s`TN?v?`wy`U&SbB|h=+Js~kZ&LwxkbtDxw7;MU2#lh)GKL`G4IZp3|x^< zxIt+Yo|k=QXyd&1uAqE>DCC+Ou_Ga?fNx(q;5mB!np=hCNMGo!*_N6UV00+E7}hFXzEzG_2HoXEomkr?FO4RlzZ z&#SAMPh1+=K)g=sgY)@1^ESjG|W9yG4zAnxG|;R zXryP)v4uo^;l&LrfsHFc!Qa9!$hjG-zIlQ3vQxrkWRQ!&q+utk#e$T&s$P0_96VlQ z#W@nS8auh#p@4W0-E4w=FEX%3tWfE(P|-dUkx8vwP;T98FO|fod9C+#x40Ftfw)NS zI{LjuQZ^0T<*LXU{8WV^j@qPLh}bi_^wu}t+ymX-pY;;#4Vvi)E(e!3F&7xoBl;IU z^JAl9B`_%ZT`_dOYM3;0wJAcg$L>d`f zQ=LnhSEtOjNGIYj=RBZ)oVGsO(NYZ`ZGEz_IUS|Y2))ydD&^sApuYP1iQ%W}cIo}d z@m3jqt19Lrlbp`mZ|h;z*Oa>6SOx{1#8#D+`q^6xMM-{bW>m{DbtpLNp{{x534P?E z%hm3A)nvoskFWe?EKCB2E$N9xNs6LBZq8(E>VQ`iBscVierX@r-mOML<4Y5Z)NWXv zDhS&){qpp~p6>U1r~sqm!}xo#ZE9>F`e#MdY;VrKC2cFGV$RLg+12%iQwbjL{dIK0 z1V7fR&>AF#$V+RTbR-MUS;oCgd`Vky91UBpqpf6e?m$H@*1BnSo;&L-*y0e^&%L1W zm;!D8G4*6Ur+MRA@Iz150DU20`!o$|YaW(!NAC?PU&>(M%V5#G|2{I6X_2K4e)Lhr z;@Y}C3A*E)L}0Xyg%iG!-4KKip;1>~BYDp?I}>&{Mr(#A3~H7H`8(^1b*l)aUu&DV zJX_wHJ&n65bR?xTwK5=gu{mdI8_U0E{&!dl)@Q(O{3jK)v_ph7;p!W{d0H(fCJqF@ zQ_Vr=Y;iCB@=SPh^tN|0<7mF}lo4s9(KTu7Qp~eQuTeMy|FbJtTqWYKHxu$d{tgb@ zy1vmq@9;Rr9lyGr%IhyqUchE|liTj~NoOF|zNKmK@T?@}g15+J%>GqNx<7MbeU)-y zmYh{7a6THdBlR|*tro(lf6@AbF#a+#UPsTdz`_+u>qx+$mpY_ok)LO*w5;m>x#A2r z@J=yXbVzQAN_%mmtGgiNeemN>lA+|D5Sqdg7ky|mp!Jv4=A^3Ex4gZ+7}Jl7bEe1Q(ML*e}UtzZ1ool2dgxs=dnPr{3n`t6FT0sw6MI1K&}J z*qhuA*9|F!5YP0%{7QP4)5}(smB$idn~DZ^B%x2+$D$rlou?yowpOlm%sML3#V*3b zG=;a~>uxCYy1EIbGC{G#x2b2N`F`p3E^9DwU+W;h7&^kVtg`c>Thb7FKP&VvUB`r#^oDfv^s6u zeI*VyQ13=oChmNyW;}2Iv>BTO%W=#^@SYTC`q{@bg~Cnt7J!|Jr0^mGL>!qEb&bV$ zf072bmi@Rk#;_xjShm4*rx@kUu)4vjHuXTHIFHQ!FdI1M)-mcy_f1u zgQ&(^?-cQV(peg{=??CnWq+*)RFBlRTo5Aa!3`#0dVl?BIBkC>&f0u8-CVg`5l^pv zfRM=Pij~+mzOHI{EU2txmlTjzds9A%d@}iYTD3W^7Se>PyM4kdo#um@1rpo;sCQP|7cBx=~bTllzr%XY*SflH=}6K&3~^&Z8bGk Hi#`1x$O7_n diff --git a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg b/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg deleted file mode 100644 index 8a23176c70ab449fcdd43b5da799197a945563ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2637 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 diff --git a/demo/public/img/framasoft.png b/demo/public/img/framasoft.png deleted file mode 100644 index 71e74c308106e6833c9a841ec4edbf07f7ecccfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19259 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 diff --git a/demo/public/img/id-150.jpg b/demo/public/img/id-150.jpg deleted file mode 100644 index ebc872a5c1180aceda7936ccfb7361ac9a4f68a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19264 zcmbrlcTiJZ^fnp=1O!AtdWlMtCen+6h)Nfb-id+;0Ria&0#SNLx^$H;Vx(8;MLsK5Ttj{gSvKO2F1^?46~jRBAY$f3D#6L6930u9@R^8o;mdY^RvGdAG= zJ}z9Op{2V-&%k(@iF!iQRlvmyG&C1!Y3S%^X{l$2Q~w9hveB{Mkkh)vVfvK*rtdZR z*Xi#Wgzwk&a+*!yMHHOvK1EsE2<*U{EkDBs3~ICN?fUAu%H}D?2CmZC?I|(z5c3%Bt#*^$m?p%`L5M z?S1_NgG0mLMnZNkCd!=vL9;=j}X?79G;`QMN1e+>J7 z*~Lb+>mn^J4K4kDc3rp_O#RTX(bC1W||As$)jNdfB}@@miNDVC;>hY$R>r; zHZ_!OG3Do^1103zrC|W;*>CIL^f;6wY9;9|3Uvs^7}4oOc*`^JfSzoUN`~IEWF}uY z2VA5-EMvap=bs!n{M*kj)Hi)^>p_rpsAxynZB&@l;r@@?pO)ux_CIu?j->Y?kRLOK zsm9n#;PxxvJSE!-ZMwYld)J@qyTl8;QI(gk@$(C+!<885%nFBJhx(DF*f8gQuc^(?}e9hf@R)%M_Vi> zAlA8uW)0i~*)zcx9BUF4u!xir?xv6zih}gdOUm8n>sw2d^EJuYy@|_|$pk7PnRsce4xcReLb7)>{QPCa_G#!8xz~ zwSE@N$5o1jNLr)|3B2bd$i{cCbpi#Ee|S4DoC(108q_~+W+mD}1iksqTArWPul$6m zV2*2<{<$$bCz)G1U(6ZiB(8gTdU~2aD`!3FVHG?NZ$ui~suz*1| zpU%g`_(V20G{35VZ~8d^H4zrdNAVd4!LbgG!O|^^518W2;ORBjKH6)C*#~v(ccI+& zcvzVZqy;$*IV77t+No3<8&3zVTA}UAGWVWkPtGY(ZC#5%!;vI~mfiVx0+heQB^(#- zw$X#}Y4>8nF4k2Fyis_KEI$YI`kw=(-njPnTOB zOa^lCxV;2d%%1X&EfZr$?01H;05UKZ1i`}l;+hA3{d_S|#9K9IV^Bw#im7Ni%{(cm z>-@ATZ8?WdKls-j$19_vq|@Y}Vacyk>yct^u>Hrx%X@m|zyrNc$zrhfJ{WbmYjDTU zbpD>5vd)X5`diCvGJlc6$+Kru5%+uKlkb48>RkBNBTOIlMh%HcJN|D8rJ%Ss6I=PG9j+rdE%7YQB8!KxD_So7^)jiq%FYuW=L#Fh{bQeiJ%6q>nG>$}}_5Xp&CgppjNsNsPYp=N?J>+LfL5 zv#E#C!!pByX-Hr#kHbr%VCuB*zjHwPW5p^pwl6eYYLggr!KUokS-pQ6=6WbTWLV(9 zx0qp*(=5uWxjXJ3spBiNqP1T}vYFS&?xzC7jkzZG`uB*%O}KOpz*!F?xgAKl#FpAT7cUR| zL_U3#v>3ZScN9gyU4?xNiUD4+dmu7}?yb~jp_E@-TK442saE`8MOsaRKNxLQx%}Sl zLruznj&Tfw-(P<%k)?mNqaQYk<`(PS&x)_EeRpGkAX!0rYuBcA_E?D>O7bJ*M9- z+)t(hXdGB@Ee&6HO>lX2hyJ^NA%Na#xfQwo;v6t2bbG?hlo~26d!`V~FROJPN9x<> zRf=V#EjT5jboXt2XkQ*%8!O^sj<>mjYuv(b?u(aC2Z!40%&hF^&4N^}bX5HgtxO>4 z66lA5h4{NNEq(|S_6H{8Bh+UO!%f%P8r6$Z^RV3<+s#)!n0$gkrpwYo0Z->}3pb@w zvp%L;zjP3g5GjgT$P{;{o8!^lVD@=<-}e$lysx<&iN1d-O(yy?5J5Aohb zgr>o84n&KiKJcM^`!%(@7; zYa%XC=+cG*r#zm0(%N3v(Ny0auDr2L^%TEW(H?V+Yh)_7~bv_ zcxgLUH&LQf6uW0OZ5Z61bf#c4`^2^|gr|D#ONg=xXmI#Wtsxz!84T1Qi_ClO_x{?t zS?3)dPpqhMV#>Dq;~J6tPU)hK@R$(G6iJJ(mGaZxzR^CVEnPD^i>?S`sIjgH$7v}akGf(UjHv5-umR4>lym`5jJ!&E>dzr^>B#xcqUnua?G@-3x8bZ!{ z-@r2i)%r8J3?)HG{=!Cq5sB-36xjw$K>!*bzzPbJYeP zjT`#!smX}v*1I9`EbT?F#M4;PJgdJN+0IUqHEqOr53 zy(MycS}V{knATc3*S95)bNSO)yHtD`%S}UpcRBjPK9YK)BfLLH=}OeX9`>BR9@0?H zJY^+N%CeL~_uWG7tQHZjjF_#-a{)yR!;KK{tW_4J_l zLtBo+>(`m7{S_hmzgd)8GCnczh~Te8Jkc7~F;mD2ypi=vYeLEpqW%0&ZFtL$R|LjF zUuTB+Y;MNB{P}d(pGlvt;RD`t039u1c)$mZqFi<)05QRRJ{O7IxGN23YDAxY;N?!i zH3Wlw`CxI{M`Etd3>-n=7_vLA36Vj@k|Zjf#;Sj7a(AFWx*_g_5lmQt6l-H_@f<~) zSc1plAU(3lE^C#;te0UA`nx1siLUQS#;LLlL@b0yP(hEt8h=FQFq%;!uLw>_ug#Ws zOetkvt#6-=?005(GjtYr{0~zSN1?|Xmtyxk`!w0`o3Tx4f;(wg2%m?YawgO*3|n$l zqfi-$K0O-n{WB-q)$d7%ED78E_GOS=sxq6`1UGz)B^VOVD3@8z9Eo|K>E@6zaFaDi zRKB?;ll=qy8D=x~Pg8+S!N!2@V6iCS+U`3#Zdo;e-oYOVjF1*npWH3}T{VqoJ@I?Z zeE(G6-&yYz29>i317q3jG3|)wf6CFrdrpD`vAetpeb3%{`1Y?!+zzdMkW+5`r|NT9 zxu=;$Id8bojjA-@{H>x`Gec?&kzHHqhD!G;EeFX9ji*6%*||958p(GA78WV zvsdz4f{+zoOaA=%JLQ2kg@}%J9M7IwAFGk`5tuI#pm7Z1s+M#K4dqG^KZMvhno%Ys zJKrSdAGmf7h}D6uNZUf}7g!L3rv0~yjc)8I{K4ix zNFN~sv)4)L@8Tw=trcnZR36jPgqOsD>@hlLVrf{a2iqBp3+5ESy+meYR!l2adIX*6 zNgUv{84^GF4TVp+@|jns55agIhf^7lsJQ118Gu~XDXH#%3L52vP}s=qGQfVqz&q{B z&<^as%{DhOZE~z=!o>Zx)Lj*q3bl#5eqn*b|D+D1PfcM7afBK{*(-lZPPY9NA?(h} zE{H+S&5Yg+*!M2UoWRuYcaDT5ZFa?kIT@!cJ`R~4OBUY#X9uZ$Iegjm$dAv2KhC@+ zF=f*nQ+=%n`(d}x&PX$L`_BnDrv!-T1tGkVF({6vlKx3rQsDG;#FFPZfU(oEf@nWr z1m$`DR~F8Jt3Is4+6Q2qzYr|{wZ3^fedo0{ycgRYTf*Hq#$R+7E4$ltrsa-)*t^z6 zK@&x>&^idgvKxI7WJIH;Dy4Bt9svnay_=31|K8sj4EAp zAQ`fjToyn_}-xy(r@82rHsqi`9SqK95E^!NN)CX+M*7o^M9{jV+n6s|6 zi(!sny@UxHwb(L?3jLUM1d?8it`}h0ww}b~4uSqKUCV}c*~4!WG7PqCCB$;_Yf8TF z_e}~$%s?)FPDebiH9Y=|7cUZK0>?G4(rZe6p09u>8~KPF-36i|Wi3rv4{mB9)db!hdk0s<8<)7=nCTLB(nsL;hwN4=X}M~d z{Yckm7Y9&W&$qD>bjR;~Yu%eYncg>lk*b&btD8s#d-W?)bARlBk%)R!{LSeKY3{*p zzD{T*Op?BZVS~RFF1~gK;oL#`_?c?Oo}TE>UiV|iPG~lrJ~-own6^eVrS+Ua68XB~ zDv5>z%0hPt){$_dcfZjE+o#%Hbr=m}d;QyuDLP?cVb!}xnZb_ouiterqEOP&KWib> zbktp3r%%k!6=58r6d9KXA}C3$RSv**k7GRt+Juq3>i+8^)vm;LSTjZz!EeuL3>umL zuvC@{WHOPcEij~y#4cH>Kc1VaG)7oEB;^fV&3gVJf4LHouoaY6E1qD;2>P-s|E=r` zU1Sa*oB12ft$!#_B+nB)@dHoXCKV$q!4z;2xt~a0L=y}vTf_vgaX9G{NCI2rf$ecu zKL_+6(82>I0zxdrBn+#-?|vf!eH2NJ0I?1OUM5!bKb+rf{N3qKA9M893p8$a%8Ri7 zb@`?I#n-XVao+2a@(*r2`a=T)-wuA-L9Khc8&P|kgmVCPd2;VAUb~AQ&O@;7-AUtt z>k@6<-tDa7bKEtR>%ZV5S_0ogDZ&i|&BW89SA@$R&B}H|MW9p2!jsmPjNps>77t0e z$8g<&Puz(=i-p(FMDf0V+R~qM1zl~v581rXl$HGcQTGuI{si?EY%Sh*m={xQQil)dOhV&_Z z*=d(X$HslsYLmXbqQlT#e-3CcA#wE&6kjAl@H;)-RDN%FlVCoesemtuX+$!^;;@Lm zJ-6QCyVD@ckoPuP6|9^~?(MNP7$k@NHXqvlb9D&Rr)<0BJ|!&D+y6#<_-jvs3xzR{ z8Vv%zW@5PG;Y!uSN-THGVK?#7i$fn?7|k&Fa)bmixPL4JDmY*I0agV?TaGtp`--G* zsmC-mH?^4A7i25@qigP*1KvMceBH{OT*u?kALmPa)Sz}u45bX;E|fwBYK_bWppE_D z)1l>3EnTZa|9toem$cP?KAK@M=gv8kJglC}a>$iN|MIYc>eqy!+Ji%X9?KGi_ft9{ zI3!VAWf%2{ICOksKKrRt4U1rchIW-X?kklr5M_spCGaItO197QxHi_Dh!EZBmAf5L zA}#3*`^l*-w@kJYM{dwu0ZUV0L#)kGsd%?2k_!HBUxXlDB{INBrQeASpOfN(p+XR1 zux_)VhLp~f;!o}7qDmY4>jPHvLDhntT1Bg430f|%9X0>a(+K!3&a8BY_Wda4W{K;! z__Q;T!WXb^XE0Hk%)a6AXL9ogX_&)_=i`y5X;h4Qwwu|Vpmwv&`UsCd=YR-w&dJEa zBrcPTFpzwk^I!&8Nu07TB-QUy`9&gPvO!Am8)Cfx_^yWn`7xc8{aAq?4_I@<{|VP@ zGXIX)=W{C(2P^CPOwxekG_f@~j@`oJfg!cyVtY-0_QHY>A-=w!xp{x~(?sgrJN!oL zJ86Qa@?uz>4}p_t`r{gc99=m9xMxV{I(74J`Zhy?R{!>>W%%y@PxMVCMjCN`+SAZUZw|x8ds|*dG77&FyE3ee#`*aL3}U z@|l+G!-H?m!?bcZHJW!5_x1|eh8RiloAHFBnE4LODp5yz%4jtiZn#!Lw^*^%)JNLRo+D96&m?S&Vy0w65Mx%BG5z0X?=_Jj3|S>$@CG?P50&7 zUzMQXE!);JRlg3qfLU~Vi_*iUa9Qr)9se`E>Bcb)VUwAF0x8#)FEff~j_RZ6I~7v) z{5;WIUu3x(!Z|6I0X~t6?gS~-h*5mh?IO|kRHp(2317wP~@69>wsss10(!vxh zz7l=$V_5L3wTx#|O;C8aPaw3K_=mDF{33rP+aD_^01b^CxqvUbE~Wohw=a!hM^l-q zH-LAS@lwMGcC&4lBI_O@+O|e~;fDTweWjCBk_lHXX5^V+;Yc9(_$%q->pufNJ>Z|R z1GT`l`4+Ac?m@CRCbrh)gHr{vVwj*j*4J{}?o@gStHgARWO6N`a`pQCsc}G4zH16W zrY9y_Vu9pLXNJ1in9S2bkF=1Z;hh0pPOgvE*`X7q z-VWK7!DsK~5!^wDf4bs0)6h-Y7 z5w0z+!l^`9A9CRwpp$Qzy=#U2i=$7w40xa`Wn+X{WrU)%ebpc3=hW9-W8>FnKqGt* zt|kk}enfp-?^j_AcN|IoGptvpX_6gQX+?mg$S^Vv*H-k7Nv%X@g-UW8mBjxnvh7(JD@ywA+ z9XsI<48JBDxdyWAdXLf#W=%QX59aK(y%BU@|B15q--{dfi;YE)>ppy>Py%k?Q^sfA z!M4j7Ay%ZN^T%H><|y8%J{L*df`!>ZT%E;cnt4=a%1399%1$$A!ylM^G|W0~r+vZc zhSh*kyGKo-sG8BBOwOczhj?lDVaM$|tlDcC{*9dfva*>~0KG7}x0`qEIf-NfJ%y12 zzdE?0LfA|u$q*KCvafD;2IE-SVT-0ywVy~VSTCVTTY_<=Z_PH_R!;k7G`nY*PHTGz zhk91erF3}T_W??$M4!bZ{*Nrd6yZM4B=Auqs55dgZ7 zK{)qo(gR!zvokCf8TnS|=CH;0moQ1SBZ1sFkIgJ`NhLM>p)W`E>%j*(z)p|b>MKFH zKK2PO1r*ZC<0=RI-eo~sr(p@^QH^tL*!A0PW8R)MJI{C1M#7Lp(ft8Egoi9EW%AT* z&pw!(hck(C5+@d5(x@JQxfE05u9gjUEu*O_(PTLidn!2rccIFw^ znzObn^6K6@e-sQPnWJ9RDlT@^Dj3O+m&088 z`RK|{xP&K)2tSi{ODJFiM98Mw_}2$#qzLVdYJQO|FQbq_R`xunWOOFN2y`TMbZ9FHk|aZ#|VAW zf84--q8$SMU|w30m6oyqZdgrFN-_I$mqo>RCPEzc+`eFOI zo_<`(sr%^-)TBZQ9ZVq+q&?!KJxr~B+j-}?nk_*EyrDeXv-qYu8~vFwjQY<&<1&k< zJfl}+%F5E#Cb{qY9U~U-M}7OK%aJjZtzulu>p$Hs!f%@II(H$eVP`4|9Zk!)Kl}4;d-PNovot{yCAQlzSX6ojWO~OY3CH{ z>uognSNwU=NxMlkF~1-ws4z3AyK}xhsD9OYbE~{gRq!VH*2k_Fm8I1rBTi7p9ufH$ z*qlou0 zg}|{?%2B6RV0ag$7)p7xA$t3zmgTqYd$CGxY5#E;icSxP6B{*06jCU6!Wxn0$Z8*3um>tedYy(|aScK^gb5D)0$wcgB+le&d zPf{1pgfPp|FDsLTer2UBm5h_@zis={UvhcbYR6HV@D7uh%=vkaUv2SWL31J{5!H4= z;otZ&rka{rW4$mlFjd4IV(YIWWJUV$`SftAB*lhCX7akrk*eCEMpxcvDeRTJD|Krn z*S1cAUqZhu{YHIPx|>iMyK0<2da1o}_E|CJLo8dxlJ=E~)L-|`e(DJgahOfE<147lj&b$ zN5?)b>~25beo~X5s1j39>IPcl`M&Oq;!b7L8xz4o6!&9^;%hWSmglxX=7ya4&jUB3 z#|{i&q(%8pzemdrcpPdNAsk7M%VBLsrhT)f*2&M%8(A(PvO6(4%{Q~$A1Axnv#DED zZ)Z7dyjxogD=RUsZTjF&$pyQtqDy+Zf#B!BNbt2IAUDj-7}aUACdk{V>skd%GmPB^ zvu&jd0tYf`931V#>!%$(rZ=$jrwl7n1z{Z_>6Q@Tm|wo~h~7Ov!y(oo9# z9raE*eQXhnOR=NEqxQ~J_MtQW4foEfDSQ9M4tf#mjc73bK7=S1Ehqh$Dn`@FUGEzP z4w@uN#;c0n6T0IH$mZd>{^SXB)ME!kFC$_t->>WkYgB}2b4D2Sed2~J6)h)H5=m7etayZq$)jS%c5;rRM}HqgVdTe#M|4T}Rw2ga<j|6d?|a8iXW3xB`8yE^l{pfjm9mJN<-e zs?I5nOBogDOb3QZ@p4gNb4tG2HADVy?@~5_)+8!#9qC484_Xlolf!9IB@N&qaP$fz z-ehPs?eC&JTnsOB4oK@#9uL8K7A%{ev6>_t@yoI~Y^gu+K>zfT)#sbrDE8UE*Blp+ zpe4t^Kw&c{7H{O+~jKHclTUg)3MX$yx=?1=M|pp5RPoXTYg-*}h1&gzV5cDC&6HvU;@a z(5uF_@>$WU5yUzOe)$}L_lA(zj<+Xl_PSk}!aq)$A&8>6uU>RQ%SaaFB$+O0SQ^G;7@WAG z*p4r6ngz1MT`*p{keMUTug@jSPQSwtddgOx&z^eLR9ZP}rJeP0pSs2sigz|PruoU8 zX%AT8nD4ax&6k$b22?|G-2uf-q>rR9f>;7UfpSHuqZ{8`<5Ft}=cdlG&sG;5;St(# zKqcoir~x~fXxE)26L8kHSC-Ep3o#+kHI51FX6T+Zv@pM;=pRN8yYh-hu86yZOc>^D z+Im~}yk8e!51g7&;*hW{zN(TbjB<2FgRg4q25Bi4CE4xeM-q+m+#B@v+5Jcf~ z0Exro_yVDY#9@6bM$E<`f8vh)2&gnt*qJIxo&ZUv;%G#4F)INP57NfUvcW>ftmcn2 z%uNYmnbniQ!NCZBQ0)C*(mGPGxCLy|-PK0a?kXO4{F$hP`!6JY`csBqP5e|A$zoz$ zi1ySJm>rcz@8oNb{$X>n;|jbrw(ig?eViG^d_eOHQRlN~iCra%p7fT)uGXvUxvr&g zLg1H)TJKBfNVmyfG@b{$^W9hUi-3o?|{(woHs28neM? zA6lgZ<<7ra)EFk1{Gi!l!zd&A)mE9MYobW9IFOa^B<0hufI7A3+=_jJw=kPAgU|C~ zJhFGpH?ur{1U^9*9ADcgTtes&%%dAdRSU%=OHd7PB}qR+Hnmaz$^azq{mXLe2J@j` z1^4}@!QPDBYjMCY^l6$G;UMxmgtP2Ii3x#wj~iQwFL`wx-@TiMuXJo~IXE)n*6x@G zY00$2V!~DQ8|ReB%Dffk(z`-#dUmRbX}MGqaSid4{`fjkn<{v=6L$zZYn8hNczTT6 zfJvO)W^Z_y7Gb|XEvEP?{&{r;8UlB~8^`%eE)&D_XNo@g1nh-i+FCBN`zV7|Ie3uM zCDbU}f+Vp$z@CtN+fi!>z!pVx_Rp`_N$h@Pr&0B&&#G4QbZJXdBmV4827fige{j74KGZ$?UB2++ zyHfuFWnHXNBAkyuH*Nu?{4bs1;*uVd!6`I98J4 z8_*#1WF#pVvuxNOAv{xl7f!xw-U1WN;MMc@3HF|7;mXxxZbrTA2i+Uts||kU^;mCH zgW7HQVDC}-1LkMh-tXM9#orjKbEFryiac{ul8<)foz4=yWm#@ki1yJI$zE)546SiL zR*Lb+%JMX8uQ4uH(=PwG?&~GxxZ@TkQ{22o(t|q@9kHtm53rSql#6iZS=PnQ8&GBB z8WOl2wHt~Un2cov)%}i;Y3cAk7N371^N3G3C16M2{!Dz}>WE@|bAC+W-#|!19r*)X zg>Z80Poz3O>jL;ViR$31%&->o);W@CDI6pR%}4dGvj4WrjqTJ9r}ouBw}be;e$R0} z9yXs|+S2z7IksBxYi_x33x*@S&H)#Q^w`zCn6ry;HEK2mv+PhfldOr$1m%%V{c?Mc zbWbNGyh==?Ii~w^TN}HHKW)Fm9LUkXQu9p10Kqhbn)-fI8>vVfBN!epH<7C-Ja9?8 zE2?)t62S-x{!E~G^B&XURFLhxmI0mOvnWxTHx}7lkqTn#$p#aC457#^Va>0zA0E< z+l+6t)6Jr3(_ER%OykKBl@ny|WU3G=5mmN*nil&B4*B4r$vO6>n)b} zp=9peBISlC-^mFYdNRLTKNwaq7G5zCt?_mDVZcfa1=K+tcGe}uUV=6j_yC8A-a+A1&6+o$2t zeXjMHy7Q&2vjIC*%zjG_fENGIKT9}Y`U`cbFx=~52iCuZDAEZrA!^L;kJ7}KJRw$= z+;t+A#Qu#@c>uY*e{CfNxs$U zbti4kTTWNx8Xdfs99xV!mP_6O1rcj$-CtPu*a$QO2u{uGq$>%U*NI3oFHZ=;BnISy zL(n^U@a@F>RVsyIRs0GjyOJ;gXpcPeoW1V!qDS#<7+tT+k~q|r%;%ooM+H) zP}IFfNLlsDp4c*AI0kAG0>PvQ6&g3bBBUsxA012Gi zf4Z4+qbX`gQx?@}hy`-Nx(B9gCKa1^s&_Q6ch8D2G#GAv{rak|DZ4KLQ!tj0yBBm> zJx7Bo+`Ye_%vR_pZ(^)K@q>|S#&IU)+-Y6c*KJ5?NpNE<93}Q0qf@LRU7h0Fi;7`9 z)6;>4Dq89f7 z5xg*Ast}z(@?WEZHQhUG9>u)eC!gwap5^NuR$D3aU(p+naOr~s2jP%{xgLD+rO&Q! zAD0?(7+h1hY~wI$6F(a#W8hmY8GLN+?dV|FDf}G?^yD^YGR~}w&kU#>%`Z|>t?}z} za%!>v-R_+RCQ|b$Esh+kDZjkzfnYxexFRDd!gENMfYy24s6#T)%5OnNRh_8ci$WDAPEL)09bi)g^VQRS@*|+Rgs%u6t;~pzhhiCgzkHcW(xS?+R_$S-=VHq!Q6MWHI67q z=&_u5fuaIDi*Zw<6dWDc%EpEN&6 zG9}Ps#k$KK9De6&Y7)3|FaJJuO2AYj4}Go>tvqW_YbPqJ$ElL@7f8cmY(%a+!Crcp zpy@Jj*r_^-Z^HlEU`m#=d0eY1Eh4p7wN%c8^ z?;H@`_T&$ao2MCVEFY!cjA~z=oB5hYV%|Xh5oOpLt$jA1VQAEbq_!J(2|g1h0CCz1 z&S(E!%517Df~X`TOs2<*9jSE|MLWEEPi0eQ^|;{gN1%IV9SM&pS2dL2T~eFyPfx1= z*9II6*HgMN-I}}W$0aKyQfDXiYex&Yf^tW&j*b*Vik5&3k zuMfW?u9A@vf+P=ean~&(>!2MZNt&g*e?K8Y#7Jdu1-MCJ-;bttscLc&4F~PGI^HeY z^N%SjIM^*a{ziWtD!_VOz7&ErL1ijNm%L>bxN?b6oHi;s?4>S7Z{jnLx`ZzdWFKG=ZOw=2TVM3cE*5M}oc`*VV%I{;4 zMl&_B-d`^t*1!BuJ~{RsP2nb^dywPlI^q_x7w>;8*E$b(B=8=UT#+3B3wn05k{mH5mq;vl z_{`$AdQKZQp5*`jH^;W-b(A7l1AYTDF4>1QG6ruo+4rJgSClLHX$7dWE$*-JPdvBsSR8IAB@z2X6Bm>iJECEtf@;f*S4 zaQl(%{Iym?Ch<TVq3NVGr({&fm@!(723Pc$@D@ zAJaEo&iAs^tk%$fz99Dm6)oUI+__;(hE_S3i|aJAxPKWJtrRQFuaT-Xb4 zp|q={+cTT95jTIYGLzh@iIVdx9DxC#2hjJBu}X)9MV(Bg_4R0sk5>Q@2W^syzxgG8 z2qh(n;KNsFX^LaLfta7zEms0oV3jU??z@zMWbvgO^{=WRi>1uurv?u7g}*+PMidTj z+w7XmerYYVkIL`=QDm&QBhgwhYqqjGO|Tx`e_ecW{x(PhXOdjP+$sFIFG>1THUS=p zDdM+?RF>fbHLIsq`uOv8NJ-`s_z@f=&Lqym!B?scGNA$ zN&br1&FB||9} zc<-f{?kXHo?a8Do^b@u2lTt$0?T%c?q%i(nD@n^g2kf;GP-f&ZQrOylbe9kw+3&f^ z2){fB0%IX)e(cngB6Ud^F9iC;AQr8<1f~O1?Cz#GqwRmab@4=@kxfi__?21J#u+0ddJum9_0+g{;9$%rQFWp0HY zMQx$x|D?6?=zFO?VF1ey;P@?235Z@bxb-Ju_pvCsSV=JG)R1g$jCVvuehyUgHd^#Z z!0!*(O}=_Rt2t|@`&Femg1f#w=fEwvEeBIBZCejNF(4;UL|`2{Gm4W`S$B|lM7V3{ zsLs%|N>9|3rGsmrR~43MS2$-)J#Q>Pg?{t<0Ou^A8U6RU9l~ zMoh21GnrBukS)DpIg|xkOuVCZNK9XoyLRxM#lhA6$)-+INkfh-Q|w0ImSy2b)%zwV zCPmHU)%9*6&?C&mU0X}(N+uO)uA64r`jXH-g)GR= zr-mj!8jJ*Zexn2(k9RCfb*0ynTEbdZ{wK%&{%EL}LehPGdJc%;6sX(nMiPA{JE#eD zzxN!Fn|l;w{@)gyG$ne%>te~=ywo>H@R#4g_a=1jTgZZ2aPt&ho z*}0zHW*J5*&ohn04wqF<%5vWma!J=@`%ly-cPk0hBrRIkN-*io1E~;DeXchBBB3q4 zUlGQH*{a)|8G7&ArxYEnP1>HlQEbljM;A>k>7^XD#3>~K+1=H}69N|F_Z26v{yu%) zQn517akI02uGVGN%jbp&0Plcy>{S%iHx)>1wbBt?Wp8AH@RHOCC(*JW5_UT+*CwyQ zf(RlyF!YSwk0Tc4^=J2Z%XA{wd$nnsgOW27{}mjw6If75U>C$fn&Vff(4U>PwElvS z<>NZH8Ms0vG{XDW`oPGujD}IW=fOeQNy;X~m@`*Z7bZ=9d{W}9!~14NCRYR!N9IH@! z%MRW7SKgz=uhs%igt8gqtCq@Q>6@nkOZ#a{Jx|nEP$}W ztIQZ~$@CB(b%#T((a^(m5by^ zPNQ}f(ZPz~63|+exGqMx!_1M4p%#8P{?I7Ggw^N=xd^KoVDaOq^}|KzVc6o1 z>gU>gUv(07EIJze@SYqY&5#Jt)PRTM;}R%xP%uA*-!WpWgh7**cyR~Q67`89Y)@>_ z4G+eqBFQb-NnTjPAat!eP9Iyc2X6MrIlYolx#D4_Te0K-QR?hM;#>MlxVt^QbaC8+ zNhW9n&ZG|*mxJIT`QvdIm6!<5MUqUKb{9UkKj{p@{$gH#+~njz50XXmEy?sY2Nrn^ zWQ*wpGGbr;GY(H3kq2d)P5Ay8m?}-7j8rGUV0|&FZIV1F1LMOvy=l+92I77y@o&HM zN82u={(nlL3lr>tcl@&rQl4(VAcrW=<2^vc*P&N@8fJV*c06MV+L+XWIfo{go!DgGs@T>4eOe zqQi*w#L|sq!5^tbugOp@SB)Dyd#^$%C8OynD5~1tkL>K;tlqj=GDf=fQRt<{O~^?1 z>Fi@}N7NCOhUq%!E>_3HlUNUU-%V5kr6TT8Lg<0zI)bbh3TUQuyHCDAgOO(t2WkN$ z=LoUP{Tsn_4#-ezyWweP6|T0Ln|;cvZ~+xOLjyi#xfnPU;eD5FggVI{^50;0>vmq3 zjDhDPw{`CLQo?_qe)?YlHVMi0znk!1{E*aQzn@R<`gwm>epZl94u0_ZfIlp)C!7vO z)+ewyJ?hmy4`?2k_U&{xsB`Qp>_#5%E8WCuyUX z%V@MM9_v$)@`yu1W44ui&Jmt4n}L!o{{T0B!WtFdg#Q3zkJ*Poy;;TGr@+sK)6TNt zWR0)3 z9Pl55BG#wSw9S9U`b%0(@k_;;ys*4h_t%nKzx*Oz8<$*roifrLG+v~3qUrw6`+xf@ z{?)$%K00`E%fp`y^{D(e;*SXE+J2j?{2%djQTTtyz6!U~?S;HnSNaRe=6fh3h?w<_ zCGWIJU@%BE6lqo!{{R#J0Ba^YKI6^`;XJQ}xMMQSaGW8U**-oNM4F{-1@RuZRqN)DY%Mx{AH^0ihr zPA%K5uHMO5{{TfHDM?-OS)}6OE@abJzTTb2JLUF(cLibyBaz6#92|E){c7?rj{g7= z;P^}MSK`ma>BtG-ABR3M@oIdFCh2r72KN3DvDhPnbqq=6vHAH>4&bNOpQ^A2cRY;r zIKdbPp~?I@j^6-(!A*Qhz7YM2{xsjsZGC$NqbGp8F?C_5BrkufXbiED z70mY0T;1H*YS(bBv7Qx>muY5-BzIrcyb|HKaes@R8u0YJw5VhFyCtQKry3W8u+z-2 zsgnhFU;eU@aA=x)UOOYFxho^DatAP&Js?|w62!kz4c!C`x<;a zKMB8O&w(BSxQ}kDrF=NibsrGPypwg}zZ1s3DDiE{R@|#K&x!S|X8Pq&$0(leRCSU# z)>Zm!I6S(X^dMt6?g{6wt_SDS=kM9i_J{aa`$c#ayiM@BeOe124{8hHUl4d}#9B@E zhvHuc>$*LpW?chb(ytT?d;4oWM$SlPv}v^cO438ASZY_$O&E{p10*DJ$2h^jCp?4K zk569azwKWojX34GWjqYwJOazGs)~wk6x1ljoFN~1MJUBvmz5;e&vX2D$73El8;Zt8 zPNKzEsamCHZfVXJT#kcn|9vIcV+zDlF;OmQnxoVw$_tgzkP@KzR$;B zyZ#iOaP)8dcl;@v;Q-{Y&VSkmvE+07s1ig6-eLzC$LZYun5Feue>dR2`67LsJ5T=r z1ApcHUHMu5XdmC_)PwxR6&Nu3vtV|>^*r-$Ug zhxm4&&ch!n@-xtP{3xQ1dH(=BzwjsiVR!t}{{XgOyT8JODTyv(9th zfnP0t+TR}aFWIB^V)(c5r$*D0RPhIfJU<1mhHWJ(Y=?>bOR8G&oNkXhC%aVFY$M<*ldDN2ETcx>hl$2^u znq1VZ;O^fmweM%7o{Qp7W!U~$Jagd>iNCRyoq6$7Q}~g8@q@x%Hq@c<2gL0I#j{HO zAy0@tD;D~vh&)Z;?P4ovq5BrCs$NfZbE0Z-+d&_lD28Cm8xNr0!+s0+siOE($8zac zmO6yC)_Sjod`Dw@bK&0{cyaF`7f9NFi99`bZK`Rv7=)~^r)qIPSz>Rq$%dke{t?I4 zo+lNIuZZNWQcB?`4#_^Zy8tIv5)D+=+3qEBSo+Pk{1g-R;@VQ{ z!#}dc)ykJ=o((tRwTw~@PyBnoh_wQT0|2bt9)^l5@jTDKZw9!l5nCCbcx}RX>{RN` zbHpsJmJ+Q=x2&p91@&iAPj@NvHrs8tCg1PA75ymuf2EhaJMlk1#8aV14+>8k46`t+ z81hY7*T-Nn73QrJz50^bb|~v#wX}UVHwhIgEOVv0ZDFbQwhsBnE622{7SnIzY{xp0*_`xlW?Vh)%=o*#Xr-i;B z>AJ0j!rfY5S^Pinm9~|l%{`UgnH9~0>hkKc+S~=6Io3jOQC{~8cvs=a0nU|95a6yH z$ZJzoWls}=%P{`{Tc;?cXAE6v)x^cFd2Lm0yDNMTj$cU+9J9PD%CjF5xQeb{imu^^ z!qBONz|*M^=eLYsIsgI_LG#QIdfsC z&Q5b}%5rjTxlPJXzQXzKw6@jY;}{;^-t-HG!QpT*#s@k4D5Acb+E4lC{{VqM^YUMD z>GMnZrT+kdOUuv63HFx$>i$FgDEmteilgu!;YAdCN&f&m{{ZkO{(ejD4rzZhzwjw} pdHEpwLw|LDA^sFmMIO?B&p-SL{{Wwo`-7Ta%`f~4US57k|JiCm43Gc- diff --git a/demo/public/img/license.png b/demo/public/img/license.png deleted file mode 100644 index 0f2a0f10722d3fabffe8af5d5eed09866e25909a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5460 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 Ng2}4L)JmC!{ST_+RpI~u diff --git a/demo/public/index.html b/demo/public/index.html deleted file mode 100644 index 7e016ef..0000000 --- a/demo/public/index.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - Bilan 2014 de l'hébergement - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  • ) and loose lists, - // which have an empty line between list items, resulting in (one or more) - // paragraphs inside the
  • . - // - // There are all sorts weird edge cases about the original markdown.pl's - // handling of lists: - // - // * Nested lists are supposed to be indented by four chars per level. But - // if they aren't, you can get a nested list by indenting by less than - // four so long as the indent doesn't match an indent of an existing list - // item in the 'nest stack'. - // - // * The type of the list (bullet or number) is controlled just by the - // first item at the indent. Subsequent changes are ignored unless they - // are for nested lists - // - lists: (function( ) { - // Use a closure to hide a few variables. - var any_list = "[*+-]|\\d+\\.", - bullet_list = /[*+-]/, - // Capture leading indent as it matters for determining nested lists. - is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), - indent_re = "(?: {0,3}\\t| {4})"; - - // TODO: Cache this regexp for certain depths. - // Create a regexp suitable for matching an li for a given stack depth - function regex_for_depth( depth ) { - - return new RegExp( - // m[1] = indent, m[2] = list_type - "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + - // m[3] = cont - "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" - ); - } - function expand_tab( input ) { - return input.replace( / {0,3}\t/g, " " ); - } - - // Add inline content `inline` to `li`. inline comes from processInline - // so is an array of content - function add(li, loose, inline, nl) { - if ( loose ) { - li.push( [ "para" ].concat(inline) ); - return; - } - // Hmmm, should this be any block level element or just paras? - var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" - ? li[li.length -1] - : li; - - // If there is already some content in this list, add the new line in - if ( nl && li.length > 1 ) - inline.unshift(nl); - - for ( var i = 0; i < inline.length; i++ ) { - var what = inline[i], - is_str = typeof what === "string"; - if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) - add_to[ add_to.length-1 ] += what; - else - add_to.push( what ); - } - } - - // contained means have an indent greater than the current one. On - // *every* line in the block - function get_contained_blocks( depth, blocks ) { - - var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), - replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), - ret = []; - - while ( blocks.length > 0 ) { - if ( re.exec( blocks[0] ) ) { - var b = blocks.shift(), - // Now remove that indent - x = b.replace( replace, ""); - - ret.push( mk_block( x, b.trailing, b.lineNumber ) ); - } - else - break; - } - return ret; - } - - // passed to stack.forEach to turn list items up the stack into paras - function paragraphify(s, i, stack) { - var list = s.list; - var last_li = list[list.length-1]; - - if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) - return; - if ( i + 1 === stack.length ) { - // Last stack frame - // Keep the same array, but replace the contents - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); - } - else { - var sublist = last_li.pop(); - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); - } - } - - // The matcher function - return function( block, next ) { - var m = block.match( is_list_re ); - if ( !m ) - return undefined; - - function make_list( m ) { - var list = bullet_list.exec( m[2] ) - ? ["bulletlist"] - : ["numberlist"]; - - stack.push( { list: list, indent: m[1] } ); - return list; - } - - - var stack = [], // Stack of lists for nesting. - list = make_list( m ), - last_li, - loose = false, - ret = [ stack[0].list ], - i; - - // Loop to search over block looking for inner block elements and loose lists - loose_search: - while ( true ) { - // Split into lines preserving new lines at end of line - var lines = block.split( /(?=\n)/ ); - - // We have to grab all lines for a li and call processInline on them - // once as there are some inline things that can span lines. - var li_accumulate = "", nl = ""; - - // Loop over the lines in this block looking for tight lists. - tight_search: - for ( var line_no = 0; line_no < lines.length; line_no++ ) { - nl = ""; - var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); - - - // TODO: really should cache this - var line_re = regex_for_depth( stack.length ); - - m = l.match( line_re ); - //print( "line:", uneval(l), "\nline match:", uneval(m) ); - - // We have a list item - if ( m[1] !== undefined ) { - // Process the previous list item, if any - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - m[1] = expand_tab( m[1] ); - var wanted_depth = Math.floor(m[1].length/4)+1; - //print( "want:", wanted_depth, "stack:", stack.length); - if ( wanted_depth > stack.length ) { - // Deep enough for a nested list outright - //print ( "new nested list" ); - list = make_list( m ); - last_li.push( list ); - last_li = list[1] = [ "listitem" ]; - } - else { - // We aren't deep enough to be strictly a new level. This is - // where Md.pl goes nuts. If the indent matches a level in the - // stack, put it there, else put it one deeper then the - // wanted_depth deserves. - var found = false; - for ( i = 0; i < stack.length; i++ ) { - if ( stack[ i ].indent !== m[1] ) - continue; - - list = stack[ i ].list; - stack.splice( i+1, stack.length - (i+1) ); - found = true; - break; - } - - if (!found) { - //print("not found. l:", uneval(l)); - wanted_depth++; - if ( wanted_depth <= stack.length ) { - stack.splice(wanted_depth, stack.length - wanted_depth); - //print("Desired depth now", wanted_depth, "stack:", stack.length); - list = stack[wanted_depth-1].list; - //print("list:", uneval(list) ); - } - else { - //print ("made new stack for messy indent"); - list = make_list(m); - last_li.push(list); - } - } - - //print( uneval(list), "last", list === stack[stack.length-1].list ); - last_li = [ "listitem" ]; - list.push(last_li); - } // end depth of shenegains - nl = ""; - } - - // Add content - if ( l.length > m[0].length ) - li_accumulate += nl + l.substr( m[0].length ); - } // tight_search - - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - // Look at the next block - we might have a loose list. Or an extra - // paragraph for the current li - var contained = get_contained_blocks( stack.length, next ); - - // Deal with code blocks or properly nested lists - if ( contained.length > 0 ) { - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - last_li.push.apply( last_li, this.toTree( contained, [] ) ); - } - - var next_block = next[0] && next[0].valueOf() || ""; - - if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { - block = next.shift(); - - // Check for an HR following a list: features/lists/hr_abutting - var hr = this.dialect.block.horizRule( block, next ); - - if ( hr ) { - ret.push.apply(ret, hr); - break; - } - - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - loose = true; - continue loose_search; - } - break; - } // loose_search - - return ret; - }; - })(), - - blockquote: function blockquote( block, next ) { - if ( !block.match( /^>/m ) ) - return undefined; - - var jsonml = []; - - // separate out the leading abutting block, if any. I.e. in this case: - // - // a - // > b - // - if ( block[ 0 ] !== ">" ) { - var lines = block.split( /\n/ ), - prev = [], - line_no = block.lineNumber; - - // keep shifting lines until you find a crotchet - while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { - prev.push( lines.shift() ); - line_no++; - } - - var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); - jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); - // reassemble new block of just block quotes! - block = mk_block( lines.join( "\n" ), block.trailing, line_no ); - } - - - // if the next block is also a blockquote merge it in - while ( next.length && next[ 0 ][ 0 ] === ">" ) { - var b = next.shift(); - block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); - } - - // Strip off the leading "> " and re-process as a block. - var input = block.replace( /^> ?/gm, "" ), - old_tree = this.tree, - processedBlock = this.toTree( input, [ "blockquote" ] ), - attr = extract_attr( processedBlock ); - - // If any link references were found get rid of them - if ( attr && attr.references ) { - delete attr.references; - // And then remove the attribute object if it's empty - if ( isEmpty( attr ) ) - processedBlock.splice( 1, 1 ); - } - - jsonml.push( processedBlock ); - return jsonml; - }, - - referenceDefn: function referenceDefn( block, next) { - var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; - // interesting matches are [ , ref_id, url, , title, title ] - - if ( !block.match(re) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var attrs = extract_attr( this.tree ); - - // make a references hash if it doesn't exist - if ( attrs.references === undefined ) - attrs.references = {}; - - var b = this.loop_re_over_block(re, block, function( m ) { - - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - var ref = attrs.references[ m[1].toLowerCase() ] = { - href: m[2] - }; - - if ( m[4] !== undefined ) - ref.title = m[4]; - else if ( m[5] !== undefined ) - ref.title = m[5]; - - } ); - - if ( b.length ) - next.unshift( mk_block( b, block.trailing ) ); - - return []; - }, - - para: function para( block ) { - // everything's a para! - return [ ["para"].concat( this.processInline( block ) ) ]; - } - }, - - inline: { - - __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { - var m, - res; - - patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; - var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); - - m = re.exec( text ); - if (!m) { - // Just boring text - return [ text.length, text ]; - } - else if ( m[1] ) { - // Some un-interesting text matched. Return that first - return [ m[1].length, m[1] ]; - } - - var res; - if ( m[2] in this.dialect.inline ) { - res = this.dialect.inline[ m[2] ].call( - this, - text.substr( m.index ), m, previous_nodes || [] ); - } - // Default for now to make dev easier. just slurp special and output it. - res = res || [ m[2].length, m[2] ]; - return res; - }, - - __call__: function inline( text, patterns ) { - - var out = [], - res; - - function add(x) { - //D:self.debug(" adding output", uneval(x)); - if ( typeof x === "string" && typeof out[out.length-1] === "string" ) - out[ out.length-1 ] += x; - else - out.push(x); - } - - while ( text.length > 0 ) { - res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); - text = text.substr( res.shift() ); - forEach(res, add ); - } - - return out; - }, - - // These characters are intersting elsewhere, so have rules for them so that - // chunks of plain text blocks don't include them - "]": function () {}, - "}": function () {}, - - __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, - - "\\": function escaped( text ) { - // [ length of input processed, node/children to add... ] - // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! - if ( this.dialect.inline.__escape__.exec( text ) ) - return [ 2, text.charAt( 1 ) ]; - else - // Not an esacpe - return [ 1, "\\" ]; - }, - - "![": function image( text ) { - - // Unlike images, alt text is plain text only. no other elements are - // allowed in there - - // ![Alt text](/path/to/img.jpg "Optional title") - // 1 2 3 4 <--- captures - var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); - - if ( m ) { - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; - - var attrs = { alt: m[1], href: m[2] || "" }; - if ( m[4] !== undefined) - attrs.title = m[4]; - - return [ m[0].length, [ "img", attrs ] ]; - } - - // ![Alt text][id] - m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); - - if ( m ) { - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion - return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; - } - - // Just consume the '![' - return [ 2, "![" ]; - }, - - "[": function link( text ) { - - var orig = String(text); - // Inline content is possible inside `link text` - var res = inline_until_char.call( this, text.substr(1), "]" ); - - // No closing ']' found. Just consume the [ - if ( !res ) - return [ 1, "[" ]; - - var consumed = 1 + res[ 0 ], - children = res[ 1 ], - link, - attrs; - - // At this point the first [...] has been parsed. See what follows to find - // out which kind of link we are (reference or direct url) - text = text.substr( consumed ); - - // [link text](/path/to/img.jpg "Optional title") - // 1 2 3 <--- captures - // This will capture up to the last paren in the block. We then pull - // back based on if there a matching ones in the url - // ([here](/url/(test)) - // The parens have to be balanced - var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); - if ( m ) { - var url = m[1]; - consumed += m[0].length; - - if ( url && url[0] === "<" && url[url.length-1] === ">" ) - url = url.substring( 1, url.length - 1 ); - - // If there is a title we don't have to worry about parens in the url - if ( !m[3] ) { - var open_parens = 1; // One open that isn't in the capture - for ( var len = 0; len < url.length; len++ ) { - switch ( url[len] ) { - case "(": - open_parens++; - break; - case ")": - if ( --open_parens === 0) { - consumed -= url.length - len; - url = url.substring(0, len); - } - break; - } - } - } - - // Process escapes only - url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; - - attrs = { href: url || "" }; - if ( m[3] !== undefined) - attrs.title = m[3]; - - link = [ "link", attrs ].concat( children ); - return [ consumed, link ]; - } - - // [Alt text][id] - // [Alt text] [id] - m = text.match( /^\s*\[(.*?)\]/ ); - - if ( m ) { - - consumed += m[ 0 ].length; - - // [links][] uses links as its reference - attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; - - link = [ "link_ref", attrs ].concat( children ); - - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion. - // Store the original so that conversion can revert if the ref isn't found. - return [ consumed, link ]; - } - - // [id] - // Only if id is plain (no formatting.) - if ( children.length === 1 && typeof children[0] === "string" ) { - - attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; - link = [ "link_ref", attrs, children[0] ]; - return [ consumed, link ]; - } - - // Just consume the "[" - return [ 1, "[" ]; - }, - - - "<": function autoLink( text ) { - var m; - - if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { - if ( m[3] ) - return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; - else if ( m[2] === "mailto" ) - return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; - else - return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; - } - - return [ 1, "<" ]; - }, - - "`": function inlineCode( text ) { - // Inline code block. as many backticks as you like to start it - // Always skip over the opening ticks. - var m = text.match( /(`+)(([\s\S]*?)\1)/ ); - - if ( m && m[2] ) - return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; - else { - // TODO: No matching end code found - warn! - return [ 1, "`" ]; - } - }, - - " \n": function lineBreak() { - return [ 3, [ "linebreak" ] ]; - } - - } - }; - - // Meta Helper/generator method for em and strong handling - function strong_em( tag, md ) { - - var state_slot = tag + "_state", - other_slot = tag === "strong" ? "em_state" : "strong_state"; - - function CloseTag(len) { - this.len_after = len; - this.name = "close_" + md; - } - - return function ( text ) { - - if ( this[state_slot][0] === md ) { - // Most recent em is of this type - //D:this.debug("closing", md); - this[state_slot].shift(); - - // "Consume" everything to go back to the recrusion in the else-block below - return[ text.length, new CloseTag(text.length-md.length) ]; - } - else { - // Store a clone of the em/strong states - var other = this[other_slot].slice(), - state = this[state_slot].slice(); - - this[state_slot].unshift(md); - - //D:this.debug_indent += " "; - - // Recurse - var res = this.processInline( text.substr( md.length ) ); - //D:this.debug_indent = this.debug_indent.substr(2); - - var last = res[res.length - 1]; - - //D:this.debug("processInline from", tag + ": ", uneval( res ) ); - - var check = this[state_slot].shift(); - if ( last instanceof CloseTag ) { - res.pop(); - // We matched! Huzzah. - var consumed = text.length - last.len_after; - return [ consumed, [ tag ].concat(res) ]; - } - else { - // Restore the state of the other kind. We might have mistakenly closed it. - this[other_slot] = other; - this[state_slot] = state; - - // We can't reuse the processed result as it could have wrong parsing contexts in it. - return [ md.length, md ]; - } - } - }; // End returned function - } - - Gruber.inline["**"] = strong_em("strong", "**"); - Gruber.inline["__"] = strong_em("strong", "__"); - Gruber.inline["*"] = strong_em("em", "*"); - Gruber.inline["_"] = strong_em("em", "_"); - - Markdown.dialects.Gruber = Gruber; - Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); - - - - var Maruku = DialectHelpers.subclassDialect( Gruber ), - extract_attr = MarkdownHelpers.extract_attr, - forEach = MarkdownHelpers.forEach; - - Maruku.processMetaHash = function processMetaHash( meta_string ) { - var meta = split_meta_hash( meta_string ), - attr = {}; - - for ( var i = 0; i < meta.length; ++i ) { - // id: #foo - if ( /^#/.test( meta[ i ] ) ) - attr.id = meta[ i ].substring( 1 ); - // class: .foo - else if ( /^\./.test( meta[ i ] ) ) { - // if class already exists, append the new one - if ( attr["class"] ) - attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); - else - attr["class"] = meta[ i ].substring( 1 ); - } - // attribute: foo=bar - else if ( /\=/.test( meta[ i ] ) ) { - var s = meta[ i ].split( /\=/ ); - attr[ s[ 0 ] ] = s[ 1 ]; - } - } - - return attr; - }; - - function split_meta_hash( meta_string ) { - var meta = meta_string.split( "" ), - parts = [ "" ], - in_quotes = false; - - while ( meta.length ) { - var letter = meta.shift(); - switch ( letter ) { - case " " : - // if we're in a quoted section, keep it - if ( in_quotes ) - parts[ parts.length - 1 ] += letter; - // otherwise make a new part - else - parts.push( "" ); - break; - case "'" : - case '"' : - // reverse the quotes and move straight on - in_quotes = !in_quotes; - break; - case "\\" : - // shift off the next letter to be used straight away. - // it was escaped so we'll keep it whatever it is - letter = meta.shift(); - /* falls through */ - default : - parts[ parts.length - 1 ] += letter; - break; - } - } - - return parts; - } - - Maruku.block.document_meta = function document_meta( block ) { - // we're only interested in the first block - if ( block.lineNumber > 1 ) - return undefined; - - // document_meta blocks consist of one or more lines of `Key: Value\n` - if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var pairs = block.split( /\n/ ); - for ( var p in pairs ) { - var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), - key = m[ 1 ].toLowerCase(), - value = m[ 2 ]; - - this.tree[ 1 ][ key ] = value; - } - - // document_meta produces no content! - return []; - }; - - Maruku.block.block_meta = function block_meta( block ) { - // check if the last line of the block is an meta hash - var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); - if ( !m ) - return undefined; - - // process the meta hash - var attr = this.dialect.processMetaHash( m[ 2 ] ), - hash; - - // if we matched ^ then we need to apply meta to the previous block - if ( m[ 1 ] === "" ) { - var node = this.tree[ this.tree.length - 1 ]; - hash = extract_attr( node ); - - // if the node is a string (rather than JsonML), bail - if ( typeof node === "string" ) - return undefined; - - // create the attribute hash if it doesn't exist - if ( !hash ) { - hash = {}; - node.splice( 1, 0, hash ); - } - - // add the attributes in - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - // return nothing so the meta hash is removed - return []; - } - - // pull the meta hash off the block and process what's left - var b = block.replace( /\n.*$/, "" ), - result = this.processBlock( b, [] ); - - // get or make the attributes hash - hash = extract_attr( result[ 0 ] ); - if ( !hash ) { - hash = {}; - result[ 0 ].splice( 1, 0, hash ); - } - - // attach the attributes to the block - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - return result; - }; - - Maruku.block.definition_list = function definition_list( block, next ) { - // one or more terms followed by one or more definitions, in a single block - var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, - list = [ "dl" ], - i, m; - - // see if we're dealing with a tight or loose block - if ( ( m = block.match( tight ) ) ) { - // pull subsequent tight DL blocks out of `next` - var blocks = [ block ]; - while ( next.length && tight.exec( next[ 0 ] ) ) - blocks.push( next.shift() ); - - for ( var b = 0; b < blocks.length; ++b ) { - var m = blocks[ b ].match( tight ), - terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), - defns = m[ 2 ].split( /\n:\s+/ ); - - // print( uneval( m ) ); - - for ( i = 0; i < terms.length; ++i ) - list.push( [ "dt", terms[ i ] ] ); - - for ( i = 0; i < defns.length; ++i ) { - // run inline processing over the definition - list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); - } - } - } - else { - return undefined; - } - - return [ list ]; - }; - - // splits on unescaped instances of @ch. If @ch is not a character the result - // can be unpredictable - - Maruku.block.table = function table ( block ) { - - var _split_on_unescaped = function( s, ch ) { - ch = ch || '\\s'; - if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) - ch = '\\' + ch; - var res = [ ], - r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), - m; - while ( ( m = s.match( r ) ) ) { - res.push( m[1] ); - s = m[2]; - } - res.push(s); - return res; - }; - - var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, - // find at least an unescaped pipe in each line - no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, - i, - m; - if ( ( m = block.match( leading_pipe ) ) ) { - // remove leading pipes in contents - // (header and horizontal rule already have the leading pipe left out) - m[3] = m[3].replace(/^\s*\|/gm, ''); - } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { - return undefined; - } - - var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; - - // remove trailing pipes, then split on pipes - // (no escaped pipes are allowed in horizontal rule) - m[2] = m[2].replace(/\|\s*$/, '').split('|'); - - // process alignment - var html_attrs = [ ]; - forEach (m[2], function (s) { - if (s.match(/^\s*-+:\s*$/)) - html_attrs.push({align: "right"}); - else if (s.match(/^\s*:-+\s*$/)) - html_attrs.push({align: "left"}); - else if (s.match(/^\s*:-+:\s*$/)) - html_attrs.push({align: "center"}); - else - html_attrs.push({}); - }); - - // now for the header, avoid escaped pipes - m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); - for (i = 0; i < m[1].length; i++) { - table[1][1].push(['th', html_attrs[i] || {}].concat( - this.processInline(m[1][i].trim()))); - } - - // now for body contents - forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { - var html_row = ['tr']; - row = _split_on_unescaped(row, '|'); - for (i = 0; i < row.length; i++) - html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); - table[2].push(html_row); - }, this); - - return [table]; - }; - - Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { - if ( !out.length ) - return [ 2, "{:" ]; - - // get the preceeding element - var before = out[ out.length - 1 ]; - - if ( typeof before === "string" ) - return [ 2, "{:" ]; - - // match a meta hash - var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); - - // no match, false alarm - if ( !m ) - return [ 2, "{:" ]; - - // attach the attributes to the preceeding element - var meta = this.dialect.processMetaHash( m[ 1 ] ), - attr = extract_attr( before ); - - if ( !attr ) { - attr = {}; - before.splice( 1, 0, attr ); - } - - for ( var k in meta ) - attr[ k ] = meta[ k ]; - - // cut out the string and replace it with nothing - return [ m[ 0 ].length, "" ]; - }; - - - Markdown.dialects.Maruku = Maruku; - Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; - Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); - - -// Include all our depndencies and; - expose.Markdown = Markdown; - expose.parse = Markdown.parse; - expose.toHTML = Markdown.toHTML; - expose.toHTMLTree = Markdown.toHTMLTree; - expose.renderJsonML = Markdown.renderJsonML; - -})(function() { - window.markdown = {}; - return window.markdown; -}()); diff --git a/demo/public/js/mustache.js b/demo/public/js/mustache.js deleted file mode 100644 index 18d92a5..0000000 --- a/demo/public/js/mustache.js +++ /dev/null @@ -1,586 +0,0 @@ -/*! - * mustache.js - Logic-less {{mustache}} templates with JavaScript - * http://github.com/janl/mustache.js - */ - -/*global define: false*/ - -(function (global, factory) { - if (typeof exports === "object" && exports) { - factory(exports); // CommonJS - } else if (typeof define === "function" && define.amd) { - define(['exports'], factory); // AMD - } else { - factory(global.Mustache = {}); // - - - diff --git a/requirements.txt b/requirements.txt index 992beb2..0024e5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,29 @@ +APScheduler==3.5.3 +astroid==2.0.4 attrs==17.4.0 chardet==3.0.4 click==6.7 clize==4.0.3 docutils==0.14 Flask==0.12.2 +Flask-APScheduler==1.10.1 Flask-Cors==3.0.3 +isort==4.3.4 itsdangerous==0.24 Jinja2==2.10 jsonschema==2.6.0 +lazy-object-proxy==1.3.1 Markdown==2.6.11 MarkupSafe==1.0 +mccabe==0.6.1 od==1.0 peewee==2.10.2 -pika==0.11.2 +pylint==2.1.1 PyRSS2Gen==1.1 +python-dateutil==2.7.3 +pytz==2018.5 sigtools==2.0.1 six==1.11.0 +tzlocal==1.5.1 Werkzeug==0.14.1 +wrapt==1.10.11 From a06db608bc7330dd1a9ff34ab264996c4e65d1ea Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 2 Sep 2018 11:42:24 +0200 Subject: [PATCH 234/586] minimalism --- .gitignore | 4 + README.md | 16 +- app/conf/schema.py | 60 +- app/core/__init__.py | 35 +- app/core/cron.py | 47 + app/core/database.py | 4 +- app/core/mailer.py | 3 + app/core/processor.py | 407 +--- app/interface/form.py | 52 +- app/interface/mail.py | 25 - app/interface/reader.py | 29 - app/interface/report.py | 78 - app/interface/rmqclient.py | 49 - app/models/comment.py | 3 +- app/models/reader.py | 17 - app/models/report.py | 22 - app/run.py | 7 +- app/templates/en/notify_reader.tpl | 9 - app/templates/en/notify_subscriber.tpl | 13 - app/templates/en/report.tpl | 42 - app/templates/en/report_message.tpl | 1 - app/templates/en/unsubscribe_page.tpl | 2 - app/templates/fr/notify_reader.tpl | 9 - app/templates/fr/notify_subscriber.tpl | 13 - app/templates/fr/report.tpl | 42 - app/templates/fr/report_message.tpl | 1 - app/templates/fr/unsubscribe_page.tpl | 2 - app/util/rabbit.py | 84 - config.json | 12 +- demo/app.js | 14 - demo/package.json | 15 - demo/public/css/font-awesome.css | 4 - demo/public/css/grids-responsive-min.css | 7 - demo/public/css/pure-0.css | 11 - demo/public/css/style.css | 176 -- demo/public/fonts/awesome/FontAwesome.otf | Bin 75188 -> 0 bytes .../fonts/awesome/fontawesome-webfont.eot | Bin 72449 -> 0 bytes .../fonts/awesome/fontawesome-webfont.svg | 504 ----- .../fonts/awesome/fontawesome-webfont.ttf | Bin 141564 -> 0 bytes .../fonts/awesome/fontawesome-webfont.woff | Bin 83760 -> 0 bytes .../OpenSans-Regular-demo.html | 364 ---- .../OpenSans-Regular-webfont.eot | Bin 19836 -> 0 bytes .../OpenSans-Regular-webfont.svg | 1831 ----------------- .../OpenSans-Regular-webfont.ttf | Bin 38232 -> 0 bytes .../OpenSans-Regular-webfont.woff | Bin 22660 -> 0 bytes .../OpenSans-Regular-cleartype.png | Bin 84913 -> 0 bytes .../specimen_files/easytabs.js | 7 - .../specimen_files/grid_12-825-55-15.css | 129 -- .../specimen_files/specimen_stylesheet.css | 396 ---- .../opensans_regular_macroman/stylesheet.css | 12 - .../img/308a3596152a79231f3feedc49afa4ef.jpg | Bin 3618 -> 0 bytes .../img/b133b66b7edc9f7ffb5cf74a87e63652.jpg | Bin 2637 -> 0 bytes .../img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg | Bin 2669 -> 0 bytes demo/public/img/framasoft.png | Bin 19259 -> 0 bytes demo/public/img/id-150.jpg | Bin 19264 -> 0 bytes demo/public/img/license.png | Bin 5460 -> 0 bytes demo/public/img/planet-link.png | Bin 13013 -> 0 bytes demo/public/index.html | 217 -- demo/public/js/markdown.js | 1740 ---------------- demo/public/js/mustache.js | 586 ------ demo/public/js/page.js | 104 - demo/public/js/stacosys.js | 77 - demo/public/redirect.html | 64 - requirements.txt | 12 +- 64 files changed, 205 insertions(+), 7153 deletions(-) create mode 100644 app/core/cron.py create mode 100644 app/core/mailer.py delete mode 100644 app/interface/mail.py delete mode 100644 app/interface/reader.py delete mode 100644 app/interface/report.py delete mode 100644 app/interface/rmqclient.py delete mode 100644 app/models/reader.py delete mode 100644 app/models/report.py delete mode 100644 app/templates/en/notify_reader.tpl delete mode 100644 app/templates/en/notify_subscriber.tpl delete mode 100644 app/templates/en/report.tpl delete mode 100644 app/templates/en/report_message.tpl delete mode 100644 app/templates/en/unsubscribe_page.tpl delete mode 100644 app/templates/fr/notify_reader.tpl delete mode 100644 app/templates/fr/notify_subscriber.tpl delete mode 100644 app/templates/fr/report.tpl delete mode 100644 app/templates/fr/report_message.tpl delete mode 100644 app/templates/fr/unsubscribe_page.tpl delete mode 100644 app/util/rabbit.py delete mode 100644 demo/app.js delete mode 100644 demo/package.json delete mode 100644 demo/public/css/font-awesome.css delete mode 100644 demo/public/css/grids-responsive-min.css delete mode 100644 demo/public/css/pure-0.css delete mode 100644 demo/public/css/style.css delete mode 100644 demo/public/fonts/awesome/FontAwesome.otf delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.eot delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.svg delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.ttf delete mode 100755 demo/public/fonts/awesome/fontawesome-webfont.woff delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf delete mode 100644 demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css delete mode 100644 demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css delete mode 100644 demo/public/fonts/opensans_regular_macroman/stylesheet.css delete mode 100644 demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg delete mode 100644 demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg delete mode 100644 demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg delete mode 100644 demo/public/img/framasoft.png delete mode 100644 demo/public/img/id-150.jpg delete mode 100644 demo/public/img/license.png delete mode 100644 demo/public/img/planet-link.png delete mode 100644 demo/public/index.html delete mode 100644 demo/public/js/markdown.js delete mode 100644 demo/public/js/mustache.js delete mode 100644 demo/public/js/page.js delete mode 100644 demo/public/js/stacosys.js delete mode 100644 demo/public/redirect.html diff --git a/.gitignore b/.gitignore index 40cc63e..921016e 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ myconfig.json db.sqlite node_modules comments.xml +stacosys/bin/ +stacosys/pyvenv.cfg +stacosys/lib64 +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index db998c1..b8dffd5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an -universal way to discuss. You can answer from any device using an e-mail client or a Webmail. +universal way to discuss. You can answer from any device using an e-mail client. ### Features overview @@ -16,25 +16,17 @@ Here is the workflow: comment is submitted - Blog administrator can approve or drop the comment by replying to e-mail - Stacosys stores approved comment in its database. - -Moreover Stacosys has an additional feature: readers can subscribe to further -comments for a post if they have provided an email. Stacosys is localized (english and french). ### Technically speaking, how does it work? -Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using [CORS](http://enable-cors.org) requests. Each blog has a unique ID. Thus Stacosys can serve multiple blogs. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. ### FAQ -*So the blog needs a server-side language?* -- It depends on your hosting configuration. Stacosys can run on a different host than the blog and it can serve several blogs. You have to change JS code embedded in blog pages to point the right Stacosys API URL. - *How do you block spammers?* -- That's a huge topic. Current comment form is basic: no captcha support but a honey - pot. Nothing prevents from improving the template with JavaScript libs to do more - complex things. +- Current comment form is basic: no captcha support but a honey pot. Second defense barrier: admin can tag comment as SPAM and, for example, link stacosys log to fail2ban tool. *Which database is used?* - Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. @@ -48,4 +40,4 @@ Stacosys can be hosted on the same server or on a different server than the blog ### Ways of improvement -Current version of Stacosys fits my needs and it serves comments on [my blog](http://blogduyax.madyanne.fr) for 6 months. However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and to make e-mail communication optional. I encourage you to fork the project and create such improvements if you need them. I'll be happy to see the project evolving and growing according to users needs. +Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. diff --git a/app/conf/schema.py b/app/conf/schema.py index 8cca55f..f35e594 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -6,9 +6,10 @@ json_schema = """ { - "$ref": "#/definitions/Stacosys", + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/Welcome", "definitions": { - "Stacosys": { + "Welcome": { "type": "object", "additionalProperties": false, "properties": { @@ -23,19 +24,15 @@ json_schema = """ }, "rss": { "$ref": "#/definitions/RSS" - }, - "rabbitmq": { - "$ref": "#/definitions/Rabbitmq" } }, "required": [ "general", "http", - "rabbitmq", "rss", "security" ], - "title": "stacosys" + "title": "Welcome" }, "General": { "type": "object", @@ -56,7 +53,7 @@ json_schema = """ "debug", "lang" ], - "title": "general" + "title": "General" }, "HTTP": { "type": "object", @@ -77,44 +74,7 @@ json_schema = """ "port", "root_url" ], - "title": "http" - }, - "Rabbitmq": { - "type": "object", - "additionalProperties": false, - "properties": { - "active": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" - }, - "vhost": { - "type": "string" - }, - "exchange": { - "type": "string" - } - }, - "required": [ - "active", - "exchange", - "host", - "password", - "port", - "username", - "vhost" - ], - "title": "rabbitmq" + "title": "HTTP" }, "RSS": { "type": "object", @@ -131,7 +91,7 @@ json_schema = """ "file", "proto" ], - "title": "rss" + "title": "RSS" }, "Security": { "type": "object", @@ -142,17 +102,13 @@ json_schema = """ }, "secret": { "type": "string" - }, - "private": { - "type": "boolean" } }, "required": [ - "private", "salt", "secret" ], - "title": "security" + "title": "Security" } } } diff --git a/app/core/__init__.py b/app/core/__init__.py index 8a6cccb..48cee40 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -5,9 +5,9 @@ import os import sys import logging from flask import Flask -from flask.ext.cors import CORS from conf import config from jsonschema import validate +from flask_apscheduler import APScheduler app = Flask(__name__) @@ -24,8 +24,6 @@ import database import processor from interface import api from interface import form -from interface import report -from interface import rmqclient # configure logging def configure_logging(level): @@ -46,26 +44,37 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) +class Config(object): + JOBS = [ + { + 'id': 'fetch_mail', + 'func': 'core.cron:fetch_mail_answers', + 'trigger': 'interval', + 'seconds': 120 + }, + { + 'id': 'submit_new_comment', + 'func': 'core.cron:submit_new_comment', + 'trigger': 'interval', + 'seconds': 60 + }, + ] + # initialize database database.setup() -# start broker client -rmqclient.start() - # start processor template_path = os.path.abspath(os.path.join(current_path, '../templates')) processor.start(template_path) -# less feature in private mode -if not config.security['private']: - # enable CORS - cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) - from app.controllers import reader - logger.debug('imported: %s ' % reader.__name__) +# cron +app.config.from_object(Config()) +scheduler = APScheduler() +scheduler.init_app(app) +scheduler.start() # tune logging level if not config.general['debug']: - logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING logger.info("Start Stacosys application") diff --git a/app/core/cron.py b/app/core/cron.py new file mode 100644 index 0000000..77f4077 --- /dev/null +++ b/app/core/cron.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import time +from core import app +from core import processor +from models.comment import Comment + +logger = logging.getLogger(__name__) + +def fetch_mail_answers(): + + logger.info('DEBUT POP MAIL') + time.sleep(80) + logger.info('FIN POP MAIL') + #data = request.get_json() + #logger.debug(data) + + #processor.enqueue({'request': 'new_mail', 'data': data}) + +def submit_new_comment(): + + for comment in Comment.select().where(Comment.notified.is_null()): + # render email body template + comment_list = ( + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.create, + "url: %s" % comment.url, + "", + "%s" % comment.message, + "", + ) + comment_text = "\n".join(comment_list) + email_body = get_template("new_comment").render(url=url, comment=comment_text) + + if clientip: + client_ips[comment.id] = clientip + + # send email + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token) + mailer.send_mail(site.admin_email, subject, email_body) + logger.debug("new comment processed ") + +def get_template(name): + return env.get_template(config.general["lang"] + "/" + name + ".tpl") diff --git a/app/core/database.py b/app/core/database.py index 7f96f5d..f6797d8 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -23,7 +23,5 @@ def provide_db(func): def setup(db): from models.site import Site from models.comment import Comment - from models.reader import Reader - from models.report import Report - db.create_tables([Site, Comment, Reader, Report], safe=True) + db.create_tables([Site, Comment], safe=True) diff --git a/app/core/mailer.py b/app/core/mailer.py new file mode 100644 index 0000000..3a1213c --- /dev/null +++ b/app/core/mailer.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + diff --git a/app/core/processor.py b/app/core/processor.py index e089f8b..b94636c 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -4,142 +4,64 @@ import os import logging import re +import PyRSS2Gen +import markdown +import json from datetime import datetime from threading import Thread from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader from models.site import Site -from models.reader import Reader -from models.report import Report from models.comment import Comment from helpers.hashing import md5 -import json from conf import config -from util import rabbit -import PyRSS2Gen -import markdown -import pika +from core import mailer + logger = logging.getLogger(__name__) queue = Queue() proc = None env = None -# store client IP in memory until classification +# keep client IP in memory until classified client_ips = {} class Processor(Thread): - def stop(self): logger.info("stop requested") self.is_running = False def run(self): - logger.info('processor thread started') + logger.info("processor thread started") self.is_running = True while self.is_running: try: msg = queue.get() - if msg['request'] == 'new_comment': - new_comment(msg['data'], msg.get('clientip', '')) - elif msg['request'] == 'new_mail': - reply_comment_email(msg['data']) - send_delete_command(msg['data']) - elif msg['request'] == 'unsubscribe': - unsubscribe_reader(msg['data']) - elif msg['request'] == 'report': - report(msg['data']) - elif msg['request'] == 'late_accept': - late_accept_comment(msg['data']) - elif msg['request'] == 'late_reject': - late_reject_comment(msg['data']) + if msg["request"] == "new_mail": + reply_comment_email(msg["data"]) + send_delete_command(msg["data"]) else: logger.info("throw unknown request " + str(msg)) except: logger.exception("processing failure") -def new_comment(data, clientip): - - logger.info('new comment received: %s' % data) - - token = data.get('token', '') - url = data.get('url', '') - author_name = data.get('author', '').strip() - author_email = data.get('email', '').strip() - author_site = data.get('site', '').strip() - message = data.get('message', '') - subscribe = data.get('subscribe', '') - - # private mode: email contains gravar md5 hash - if config.security['private']: - author_gravatar = author_email - author_email = '' - else: - author_gravatar = md5(author_email.lower()) - - # create a new comment row - site = Site.select().where(Site.token == token).get() - - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site - - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # add a row to Comment table - comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - author_gravatar=author_gravatar, - content=message, created=created, published=None) - comment.save() - - article_url = "http://" + site.url + url - - # render email body template - comment_list = ( - 'author: %s' % author_name, - 'email: %s' % author_email, - 'site: %s' % author_site, - 'date: %s' % created, - 'url: %s' % url, - '', - '%s' % message, - '' - ) - comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render( - url=article_url, comment=comment_text) - - if clientip: - client_ips[comment.id] = clientip - - # send email - subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, token) - mail(site.admin_email, subject, email_body) - - # Reader subscribes to further comments - if not config.security['private'] and subscribe and author_email: - subscribe_reader(author_email, token, url) - - logger.debug("new comment processed ") - - def reply_comment_email(data): - from_email = data['from'] - subject = data['subject'] - message = '' - for part in data['parts']: - if part['content-type'] == 'text/plain': - message = part['content'] + from_email = data["from"] + subject = data["subject"] + message = "" + for part in data["parts"]: + if part["content-type"] == "text/plain": + message = part["content"] break - m = re.search('\[(\d+)\:(\w+)\]', subject) + m = re.search("\[(\d+)\:(\w+)\]", subject) if not m: - logger.warn('ignore corrupted email. No token %s' % subject) + logger.warn("ignore corrupted email. No token %s" % subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -148,317 +70,110 @@ def reply_comment_email(data): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn('unknown comment %d' % comment_id) + logger.warn("unknown comment %d" % comment_id) return if comment.published: - logger.warn('ignore already published email. token %d' % comment_id) + logger.warn("ignore already published email. token %d" % comment_id) return if comment.site.token != token: - logger.warn('ignore corrupted email. Unknown token %d' % comment_id) + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) return if not message: - logger.warn('ignore empty email') + logger.warn("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ('NO', 'SP'): + if message[:2].upper() in ("NO", "SP"): # put a log to help fail2ban - if message[:2].upper() == 'SP': # SPAM + if message[:2].upper() == "SP": # SPAM if comment_id in client_ips: - logger.info('SPAM comment from %s: %d' % - (client_ips[comment_id], comment_id)) + logger.info( + "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + ) else: - logger.info('cannot identify SPAM source: %d' % comment_id) + logger.info("cannot identify SPAM source: %d" % comment_id) # forget client IP if comment_id in client_ips: del client_ips[comment_id] - # report event - report_rejected(comment) - - logger.info('discard comment: %d' % comment_id) + logger.info("discard comment: %d" % comment_id) comment.delete_instance() - email_body = get_template('drop_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) + email_body = get_template("drop_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) else: - # report event - report_published(comment) - # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('commit comment: %d' % comment_id) + logger.info("commit comment: %d" % comment_id) # rebuild RSS rss(token) # send approval confirmation email to admin - email_body = get_template('approve_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) - - # notify reader once comment is published - if not config.security['private']: - reader_email = get_email_metadata(message) - if reader_email: - notify_reader(from_email, reader_email, comment.site.token, - comment.site.url, comment.url) - - # notify subscribers every time a new comment is published - notify_subscribed_readers( - comment.site.token, comment.site.url, comment.url) - - -def late_reject_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_rejected(comment) - - # delete Comment row - comment.delete_instance() - - logger.info('late reject comment: %s' % id) - - -def late_accept_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_published(comment) - - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() - - logger.info('late accept comment: %s' % id) + email_body = get_template("approve_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) def get_email_metadata(message): # retrieve metadata reader email from email body sent by admin email = "" - m = re.search('email:\s(.+@.+\..+)', message) + m = re.search(r"email:\s(.+@.+\..+)", message) if m: email = m.group(1) return email -def subscribe_reader(email, token, url): - logger.info('subscribe reader %s to %s [%s]' % (email, url, token)) - recorded = Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url).count() - if recorded: - logger.debug('reader %s is already recorded' % email) - else: - site = Site.select().where(Site.token == token).get() - reader = Reader(site=site, email=email, url=url) - reader.save() - - # report event - report_subscribed(reader) - - -def unsubscribe_reader(data): - token = data.get('token', '') - url = data.get('url', '') - email = data.get('email', '') - logger.info('unsubscribe reader %s from %s (%s)' % (email, url, token)) - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url): - # report event - report_unsubscribed(reader) - - reader.delete_instance() - - -def notify_subscribed_readers(token, site_url, url): - logger.info('notify subscribers for %s (%s)' % (url, token)) - article_url = "http://" + site_url + url - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.url == url): - to_email = reader.email - logger.info('notify reader %s' % to_email) - unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( - config.http['root_url'], to_email, token, reader.url) - email_body = get_template( - 'notify_subscriber').render(article_url=article_url, - unsubscribe_url=unsubscribe_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def notify_reader(from_email, to_email, token, site_url, url): - logger.info('notify reader: email %s about URL %s' % (to_email, url)) - article_url = "http://" + site_url + url - email_body = get_template('notify_reader').render(article_url=article_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def report_rejected(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - rejected=True) - report.save() - - -def report_published(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - published=True) - report.save() - - -def report_subscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - subscribed=True) - report.save() - - -def report_unsubscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - unsubscribed=True) - report.save() - - -def report(token): - site = Site.select().where(Site.token == token).get() - - standbys = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published.is_null(True)): - standbys.append({'url': "http://" + site.url + row.url, - 'created': row.created.strftime('%d/%m/%y %H:%M'), - 'name': row.author_name, 'content': row.content, - 'id': row.id}) - - published = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.published): - published.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - rejected = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.rejected): - rejected.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - subscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - subscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - unsubscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - unsubscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - email_body = get_template('report').render(secret=config.security['secret'], - root_url=config.http[ - 'root_url'], - standbys=standbys, - published=published, - rejected=rejected, - subscribed=subscribed, - unsubscribed=unsubscribed) - subject = get_template('report_message').render(site=site.name) - - mail(site.admin_email, subject, email_body) - - # delete report table - Report.delete().execute() - - def rss(token, onstart=False): - if onstart and os.path.isfile(config.rss['file']): + if onstart and os.path.isfile(config.rss["file"]): return site = Site.select().where(Site.token == token).get() - rss_title = get_template('rss_title_message').render(site=site.name) + rss_title = get_template("rss_title_message").render(site=site.name) md = markdown.Markdown() items = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published).order_by( - -Comment.published).limit(10): - item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) - items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.rss['proto'], - row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), - pubDate=row.published - )) + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (config.rss["proto"], site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (config.rss["proto"], row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) + ) rss = PyRSS2Gen.RSS2( title=rss_title, - link='%s://%s' % (config.rss['proto'], site.url), + link="%s://%s" % (config.rss["proto"], site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), - items=items) - rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') - - -def get_rabbitmq_connection(): - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] + items=items, ) - return rabbit.Connection(parameters) - -def mail(to_email, subject, message): - - body = { - 'to': to_email, - 'subject': subject, - 'content': message - } - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.send', - body=json.dumps(body, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email for %s posted' % to_email) + rss.write_xml(open(config.rss["file"], "w"), encoding="utf-8") def send_delete_command(content): - - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.delete', - body=json.dumps(content, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email accepted. Delete request sent for %s' % content) + # TODO delete mail + pass def get_template(name): - return env.get_template(config.general['lang'] + '/' + name + '.tpl') + return env.get_template(config.general["lang"] + "/" + name + ".tpl") def enqueue(something): diff --git a/app/interface/form.py b/app/interface/form.py index 42d6f2c..e1e67ec 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -2,46 +2,70 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort, redirect +from datetime import datetime +from flask import request, abort, redirect from core import app from models.site import Site from models.comment import Comment from helpers.hashing import md5 -from core import processor logger = logging.getLogger(__name__) -@app.route("/newcomment", methods=['POST']) + +@app.route("/newcomment", methods=["POST"]) def new_form_comment(): try: data = request.form # add client IP if provided by HTTP proxy - clientip = '' - if 'X-Forwarded-For' in request.headers: - clientip = request.headers['X-Forwarded-For'] - - # log + ip = "" + if "X-Forwarded-For" in request.headers: + ip = request.headers["X-Forwarded-For"] + + # log logger.info(data) # validate token: retrieve site entity - token = data.get('token', '') + token = data.get("token", "") site = Site.select().where(Site.token == token).get() if site is None: - logger.warn('Unknown site %s' % token) + logger.warn("Unknown site %s" % token) abort(400) # honeypot for spammers - captcha = data.get('captcha', '') + captcha = data.get("captcha", "") if captcha: - logger.warn('discard spam: data %s' % data) + logger.warn("discard spam: data %s" % data) abort(400) - processor.enqueue({'request': 'new_comment', 'data': data, 'clientip': clientip}) + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").to_lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") + + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # add a row to Comment table + comment = Comment( + site=site, + url=url, + author_name=author_name, + author_site=author_site, + author_gravatar=author_gravatar, + content=message, + created=created, + notified=None, + published=None, + ip=ip, + ) + comment.save() except: logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) \ No newline at end of file + return redirect("/redirect/", code=302) diff --git a/app/interface/mail.py b/app/interface/mail.py deleted file mode 100644 index ccebbb9..0000000 --- a/app/interface/mail.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/inbox", methods=['POST']) -def new_mail(): - - try: - data = request.get_json() - logger.debug(data) - - processor.enqueue({'request': 'new_mail', 'data': data}) - - except: - logger.exception("new mail failure") - abort(400) - - return "OK" diff --git a/app/interface/reader.py b/app/interface/reader.py deleted file mode 100644 index fb6149b..0000000 --- a/app/interface/reader.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/unsubscribe", methods=['GET']) -def unsubscribe(): - - try: - data = { - 'token': request.args.get('token', ''), - 'url': request.args.get('url', ''), - 'email': request.args.get('email', '') - } - logger.debug(data) - - processor.enqueue({'request': 'unsubscribe', 'data': data}) - - except: - logger.exception("unsubscribe failure") - abort(400) - - return "OK" diff --git a/app/interface/report.py b/app/interface/report.py deleted file mode 100644 index 297b76c..0000000 --- a/app/interface/report.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from conf import config -from flask import request, jsonify, abort -from core import app -from models.site import Site -from models.comment import Comment -from helpers.hashing import md5 -from core import processor - -logger = logging.getLogger(__name__) - -@app.route("/report", methods=['GET']) -def report(): - - try: - token = request.args.get('token', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(404) - - processor.enqueue({'request': 'report', 'data': token}) - - - except: - logger.exception("report failure") - abort(500) - - return "OK" - - -@app.route("/accept", methods=['GET']) -def accept_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_accept', 'data': id}) - - except: - logger.exception("accept failure") - abort(500) - - return "PUBLISHED" - - -@app.route("/reject", methods=['GET']) -def reject_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_reject', 'data': id}) - - except: - logger.exception("reject failure") - abort(500) - - return "REJECTED" diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py deleted file mode 100644 index 51830fb..0000000 --- a/app/interface/rmqclient.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import pika -from conf import config -from threading import Thread -import logging -import json -from core import processor -from util import rabbit - -logger = logging.getLogger(__name__) - - -class MailConsumer(rabbit.Consumer): - - def process(self, channel, method, properties, body): - try: - topic = method.routing_key - data = json.loads(body) - - if topic == 'mail.message': - if "STACOSYS" in data['subject']: - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - else: - logger.info('ignore message => {}'.format(data)) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) - except: - logger.exception('cannot process message') - - -def start(): - - logger.info('start rmqclient') - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] - ) - - connection = rabbit.Connection(parameters) - c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') - c.start() diff --git a/app/models/comment.py b/app/models/comment.py index 502f0bf..10f7301 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,11 +13,12 @@ from core.database import get_db class Comment(Model): url = CharField() created = DateTimeField() + notified = DateTimeField(null=True,default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_email = CharField(default='') author_site = CharField(default='') author_gravatar = CharField(default='') + ip = CharField(default='') content = TextField() site = ForeignKeyField(Site, related_name='site') diff --git a/app/models/reader.py b/app/models/reader.py deleted file mode 100644 index 212c74b..0000000 --- a/app/models/reader.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - - -class Reader(Model): - url = CharField() - email = CharField(default='') - site = ForeignKeyField(Site, related_name='reader_site') - - class Meta: - database = get_db() diff --git a/app/models/report.py b/app/models/report.py deleted file mode 100644 index bda6e68..0000000 --- a/app/models/report.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import BooleanField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - -class Report(Model): - name = CharField() - email = CharField() - url = CharField() - published = BooleanField(default=False) - rejected = BooleanField(default=False) - subscribed = BooleanField(default=False) - unsubscribed = BooleanField(default=False) - site = ForeignKeyField(Site, related_name='report_site') - - class Meta: - database = get_db() diff --git a/app/run.py b/app/run.py index d1cfec0..a46b640 100644 --- a/app/run.py +++ b/app/run.py @@ -3,7 +3,7 @@ import logging import json -from clize import clize, run +from clize import Clize, run from jsonschema import validate from conf import config, schema @@ -15,20 +15,19 @@ def load_json(filename): return jsondoc -@clize +@Clize def stacosys_server(config_pathname): # load and validate startup config conf = load_json(config_pathname) json_schema = json.loads(schema.json_schema) - v = validate(conf, json_schema) + validate(conf, json_schema) # set configuration config.general = conf['general'] config.http = conf['http'] config.security = conf['security'] config.rss = conf['rss'] - config.rabbitmq = conf['rabbitmq'] # start application from core import app diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl deleted file mode 100644 index 701b55f..0000000 --- a/app/templates/en/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Hi, - -Your comment has been approved. It should be published in few minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl deleted file mode 100644 index 5a81758..0000000 --- a/app/templates/en/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Hi, - -A new comment has been published for an article you have subscribed to. - - {{ article_url }} - -You can unsubscribe at any time using this link: - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl deleted file mode 100644 index 0960555..0000000 --- a/app/templates/en/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NEW SUBSCRIPTIONS{% else %}NEW SUBSCRIPTION{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}CANCELLED SUBSCRIPTIONS{% else %}CANCELLED SUBSCRIPTION{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}PUBLISHED COMMENTS{% else %}PUBLISHED COMMENT{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}REJECTED COMMENTS{% else %}REJECTED COMMENT{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}STANDBY COMMENTS{% else %}STANDBY COMMENT{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/en/report_message.tpl b/app/templates/en/report_message.tpl deleted file mode 100644 index c1b5056..0000000 --- a/app/templates/en/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Status report : {{ site }} \ No newline at end of file diff --git a/app/templates/en/unsubscribe_page.tpl b/app/templates/en/unsubscribe_page.tpl deleted file mode 100644 index a52afd7..0000000 --- a/app/templates/en/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Your request has been sent. In case of issue please contact site -administrator. diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl deleted file mode 100644 index 9464cb0..0000000 --- a/app/templates/fr/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour, - -Votre commentaire a été approuvé. Il sera publié dans quelques minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl deleted file mode 100644 index 1d79f8a..0000000 --- a/app/templates/fr/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Bonjour, - -Un nouveau commentaire a été publié pour un article auquel vous êtes abonné. - - {{ article_url }} - -Vous pouvez vous désinscrire à tout moment en suivant ce lien : - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl deleted file mode 100644 index 9c8f52b..0000000 --- a/app/templates/fr/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NOUVEAUX ABONNEMENTS{% else %}NOUVEL ABONNEMENT{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}ABONNEMENTS RESILIES{% else %}ABONNEMENT RESILIE{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}COMMENTAIRES PUBLIES{% else %}COMMENTAIRE PUBLIE{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}COMMENTAIRES REJETES{% else %}COMMENTAIRE REJETE{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}COMMENTAIRES EN ATTENTE{% else %}COMMENTAIRE EN ATTENTE{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/fr/report_message.tpl b/app/templates/fr/report_message.tpl deleted file mode 100644 index 45f08e7..0000000 --- a/app/templates/fr/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Rapport d'activité : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/unsubscribe_page.tpl b/app/templates/fr/unsubscribe_page.tpl deleted file mode 100644 index 3cd63e8..0000000 --- a/app/templates/fr/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Votre requête a été envoyée. En cas de problème, contactez l'administrateur du -site. diff --git a/app/util/rabbit.py b/app/util/rabbit.py deleted file mode 100644 index 042e272..0000000 --- a/app/util/rabbit.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 - *- - -import logging -import pika -import time -from threading import Thread - -logger = logging.getLogger(__name__) - -EXCHANGE_TYPE = "topic" -CONNECT_DELAY = 3 - -class Connection: - - def __init__(self, connection_parameters): - self._connection_parameters = connection_parameters - - def open(self): - - self._connection = None - while True: - try: - self._connection = pika.BlockingConnection( - self._connection_parameters) - break - except: - time.sleep(CONNECT_DELAY) - logger.exception('rabbitmq connection failure. try again...') - return self._connection - - def close(self): - self._connection.close() - self._connection = None - - def get(self): - return self._connection - - -class Consumer(Thread): - - _connector = None - _channel = None - _queue_name = None - - def __init__(self, connector, exchange_name, routing_key): - Thread.__init__(self) - self._connector = connector - self._exchange_name = exchange_name - self._routing_key = routing_key - - def configure(self, connection): - - self._channel = None - while True: - try: - - self._channel = connection.channel() - self._channel.exchange_declare( - exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE - ) - - result = self._channel.queue_declare(exclusive=True) - self._queue_name = result.method.queue - self._channel.queue_bind( - exchange=self._exchange_name, - queue=self._queue_name, - routing_key=self._routing_key, - ) - break - except: - logger.exception('configuration failure. try again...') - time.sleep(CONNECT_DELAY) - - def run(self): - - self._connector.open() - self.configure(self._connector.get()) - self._channel.basic_consume( - self.process, queue=self._queue_name, no_ack=True) - self._channel.start_consuming() - - def process(self, channel, method, properties, body): - raise NotImplemented diff --git a/config.json b/config.json index 1681d40..5abcdfc 100755 --- a/config.json +++ b/config.json @@ -11,20 +11,10 @@ }, "security": { "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", - "secret": "Uqca5Kc8xuU6THz9", - "private": true + "secret": "Uqca5Kc8xuU6THz9" }, "rss": { "proto": "http", "file": "comments.xml" - }, - "rabbitmq": { - "active": true, - "host": "rabbit", - "port": 5672, - "username": "techuser", - "password": "tech", - "vhost": "devhub", - "exchange": "hub.topic" } } diff --git a/demo/app.js b/demo/app.js deleted file mode 100644 index aade06b..0000000 --- a/demo/app.js +++ /dev/null @@ -1,14 +0,0 @@ -var http = require('http'); -var finalhandler = require('finalhandler'); -var serveStatic = require('serve-static'); - -var serve = serveStatic("./public"), - port = 9000;; - -var server = http.createServer(function(req, res){ - var done = finalhandler(req, res) - serve(req, res, done) -}); - -server.listen(port); -console.log('serve static resources at http://localhost:%d', port); diff --git a/demo/package.json b/demo/package.json deleted file mode 100644 index 5876a71..0000000 --- a/demo/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "website", - "version": "0.1.0", - "description": "Web site", - "main": "app.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Yax", - "license": "BSD", - "dependencies": { - "finalhandler": "~0.3.3", - "serve-static": "~1.9.1" - } -} diff --git a/demo/public/css/font-awesome.css b/demo/public/css/font-awesome.css deleted file mode 100644 index c0a5993..0000000 --- a/demo/public/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/awesome/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/awesome/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/awesome/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/awesome/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/awesome/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} diff --git a/demo/public/css/grids-responsive-min.css b/demo/public/css/grids-responsive-min.css deleted file mode 100644 index 7bcfd32..0000000 --- a/demo/public/css/grids-responsive-min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} \ No newline at end of file diff --git a/demo/public/css/pure-0.css b/demo/public/css/pure-0.css deleted file mode 100644 index 14497d9..0000000 --- a/demo/public/css/pure-0.css +++ /dev/null @@ -1,11 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -/*! -normalize.css v1.1.3 | MIT License | git.io/normalize -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} \ No newline at end of file diff --git a/demo/public/css/style.css b/demo/public/css/style.css deleted file mode 100644 index 230b848..0000000 --- a/demo/public/css/style.css +++ /dev/null @@ -1,176 +0,0 @@ -@font-face{ - font-family: 'open_sansregular'; - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot'); - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot?iefix') format('eot'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg#webfont') format('svg'); - font-weight: normal; - font-style: normal; -} - -body, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"] { - font-family: "open_sansregular"; - //font-size: 14px; -} - -h1, h2, h3 { - font-size: 33.75px; -} - -a { - color: #045DB7; - text-decoration: none; -} - -a:hover, a:focus { - text-decoration: underline; -} - -i { - margin: 2px; -} - -pre { - background-color: #F5F5F5; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px 4px 4px 4px; - display: block; - font-size: 14px; - line-height: 21px; - margin: 0 0 10.5px; - padding: 10px; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; -} -#banner { - background-color: #EEEEEE; - min-height: 20px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - font-size: 20px; - font-weight: bold; -} - -#banner a { - color: #666666; -} - -#banner a:hover, a:focus { - color: #0099DD; -} - -#banner p { - display:inline; - font-weight: normal; - margin: 0 9px; -} - -.l-box { - padding: 1em; -} - -.tag-1 { - font-size : 12pt; -} - -.tag-2 { - font-size : 10pt; -} - -.tag-3 { - font-size : 8pt; -} - -.well { - background-color: #EEEEEE; - border: 1px solid #DCDCDC; - border-radius: 4px 4px 4px 4px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - margin-bottom: 20px; - min-height: 20px; - padding: 19px; -} - -.title { - font-weight: bold; -} - -#sidebar i { - margin-right: 5px; -} - -.nolist { - list-style: none outside none; - margin-left: 0; - padding-left: 15px; -} - -.label { - background-color: #CCCCCC; - color: #FFFFFF; - font-weight: bold; - display: inline-block; - padding: 2px 4px; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - vertical-align: baseline; - white-space: nowrap; - border-radius: 3px 3px 3px 3px; -} - -.comment { - clear: both; - display: block; -} - -.inline { - display:inline; -} - -#footer { - background: none repeat scroll 0 0 #EEEEEE; - border-top: 7px solid #000000; - clear: both; - color: #444444; - margin-top: 30px; - padding: 15px; -} - -.divhidden { - left: -9999px; - position: relative; -} - -.preview-markdown { - border-radius: 10px 10px 10px 10px; - -moz-border-radius: 10px 10px 10px 10px; - -webkit-border-radius: 10px 10px 10px 10px; - border: 1px dashed #000000; - background: none repeat scroll 0 0 #a0bbb6; - padding: 10px; -} - -.button-success, -.button-error, -.button-warning, -.button-secondary { - color: white; - border-radius: 4px; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} - -.button-success { - background: rgb(28, 184, 65); /* this is a green */ -} - -.button-error { - background: rgb(202, 60, 60); /* this is a maroon */ -} - -.button-warning { - background: rgb(223, 117, 20); /* this is an orange */ -} - -.button-secondary { - background: rgb(66, 184, 221); /* this is a light blue */ -} diff --git a/demo/public/fonts/awesome/FontAwesome.otf b/demo/public/fonts/awesome/FontAwesome.otf deleted file mode 100644 index 3461e3fce6a37f2321ecbe64707f04c0a4f05424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75188 zcmd42cYG5^*C@QQyeoNEmI+v3OJ1!hp?BN#Bql&0F(rUQ=*C61jEjsU_uhM#yN!)a zZ=nSOfy5~U0x2Yzbn+xmdPp$|WF(Ia_sq&BJV=l2St!J@u6Ld}hLIigG(oNmSNVYp2bu*`8&mCT(g%JrKs|W6VjPjD7@?@<57`EsF_Gij(~7S;$jX z2uy5njLz_R|~-giuu0_{g+f+ve88OQ{Hz6>Y}qGm4HH8LdoEP_Ir~wHA13xKq37E z1Q7#%ImkKEQfdVC%s|@tAvjG9rGf|G%tLS)wVE;wz~z*JdUGJ{Lb24ffiy>{sLuw{ zN=i%p4&x(nc7ggcB(4K#2{l|&I*@jvl#*QoX(=^T^1?vc`5_#d8Y8(m0B0V8%cxE# z$pBnVc{p_qq+KX?r%B0{5Tf&5n`|=c zVocso$A%h=aRy_sSr<0ddtr36w}@);HtI||V*&u5GQ^q}ChAOv7#*33dEO5J<`I%J z*XfL=NJFf*@6;JnrxS?Jv(dU>lMZNv>x|xJgST0)^ZvUTCS9nR;D;OFCALM<4|cS0 zYNX*m0;fd-nOKu<8nuWrP;pc;Wuzjh2ue?xfq7<9)2SJhFQgVyVemeL(m{GHV42{( zj*5ZUn|hjxr9$DY5z3R_VDViTHB~GZO+`ceH&s%?2xUzWj8p>r63nNdWGs|hNF|Ez z3_x2)t$`3h#RG+4z;(3FM9l*V{~4dWakP0RwGPS}p_WLIvN!Z%D)eP4^k@*r2UcJ- zU?=@*H?{x2(58Ba^p5QH?|rs+TW>_~_TISOtlp~Lj^1Ov-M!a( zzv}J1P2C=H+Z$+4xIO*0|LtdQZ@V3LJL`7U?T*{WZg=0lcKa(r`~KGyb$|2y)%O?P zpZ?_!U)FuOW{B$$#SnIg%Mkex*jk1-50MUW8X_4&x!rfW>vqTOPq#nZZoBom{jU7N z?Kihy-F|Vq<@U4NPi{ZD{owY!+jnl?x_#sJZ|;=H59|LA1MdHSc=vyM#lTJ^gTau`vLeC!n{ysFfXP_Uc`a;;df9b8Q`%5CI-q;j?f_Z z08@+f2t13LIfyd|dpIWbJ7SE2M+X6Y`2Iv zkp55o6=8%9;E*zdF@cM1gm+?lAU^<05&JWMOK=9?GfrY#nxQ=#37!98@E7s2C_kX( zC)VL8>pEDTzy|wG(u4WIx(CZZyip8U549cAFn<07M;srB30*Ni03+$ax};f-cVgcG z?mU=>^dM|7CT$V}dFOaPnef&?TC8tyti(D1AN0WcgLMzq`5)sfN=5Jn`%Q2L%ZV|e zl|>C1nDg+#cYyEwFueh|8;M1@qnrlv{tx2;EpI}L@Bj%;S(HBnvCo4r5Z3J3VAh|L z<;C|91Fq}f+8ik7{a@>YGGgBWt|0H6vm9+D_>zG%!GU*vgSez_v3`gN?0**q@gSZe z&4DsfsLMf%#B&~$%c2BmvjBC70pNldvK)OGz|)9*7$^_8{)72JI)izrZzX|2bz&g6 z5X63xu^GT)2Fno{M$)8hgZ%>hi3CQMO{9n`r5)Xz4^*h=``X=^^&>Ji>7z6GQLVJL| z_aKddx*yOSg#T~iaf59p@jw_#VuBCxViH3?=0bWXsoR0$7|#Km$Kk!l!}JNx|I;6t zQ24u_Le9kh{ZB$TOd^pe9yTF>YR?YaZFd!x{0fp_1!PlmxoWQIAlbaCRI;O$No?ms&tQPAvhI zcLfzlZKi@i0oH?3y#tirbSj%Fr|PH{s)IU29j9KRy5UrMnfj3Wg8G5#rT(J&;SlaD zkxPb1R1%Fukc^g0koZa-mn@S6N`fTOk~m4Kq)<{PX_a(J4ogl*PD{>7UX{Elxh%OM z`M2a}$#0TBB}j75iFF$8q;VSOd9(^983P8*!UoeWONP6bYtPEAf-PDhGoA5e9%B|W&ob+o%}g+} zor!0%nS7>*zlX3A#CmdZBB z^s;1GuB<{3O7?^7SJ@vjyIdjH$RCx@kk69Omp?9FB3~hY zP97?ck{jen@yA4hRgPR6M?4L6qa z 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�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 DgjuED diff --git a/demo/public/fonts/awesome/fontawesome-webfont.eot b/demo/public/fonts/awesome/fontawesome-webfont.eot deleted file mode 100755 index 6cfd56609567bc9db55186415c694d1d32808fc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72449 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
    U~M!&>g0b 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 diff --git a/demo/public/fonts/awesome/fontawesome-webfont.svg b/demo/public/fonts/awesome/fontawesome-webfont.svg deleted file mode 100755 index a9f8469..0000000 --- a/demo/public/fonts/awesome/fontawesome-webfont.svg +++ /dev/null @@ -1,504 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/awesome/fontawesome-webfont.ttf b/demo/public/fonts/awesome/fontawesome-webfont.ttf deleted file mode 100755 index 5cd6cff6d6f6cf438a882e366420dbcc5dddd3f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141564 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ƓCICn66&u!PHl6pn& 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%0D%8GQ!Ai?6y^fVUY%4hi>cI9O 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
    KT23~?3Dw&p)A=062yj$!;@;Sv13 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 - - - - - - - - - - - -Open Sans Regular Specimen - - - - - - -
    - - - -
    - - -
    - -
    -
    -
    AaBb
    -
    -
    - -
    -
    A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
    -
    -
    -
    - - - - - - - - - - - - - - - - -
    10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    - -
    - -
    - - - -
    - - -
    -
    ◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
    body
    body
    body
    -
    - bodyOpen Sans Regular -
    -
    - bodyArial -
    -
    - bodyVerdana -
    -
    - bodyGeorgia -
    - - - -
    - - -
    - -
    -

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    -
    -
    -

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    - -
    - -
    - -
    -
    -

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    -

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    - -
    - -
    - -
    -
    -

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    - -
    - - - -
    -
    -

    10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    - -
    -
    -

    14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    -

    18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    - -
    -
    - -
    - -
    -
    -

    20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    -

    24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    - -
    - -
    - -
    -
    -

    30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

    -
    -
    - -
    - - - - -
    - -
    - -
    - -
    -

    Lorem Ipsum Dolor

    -

    Etiam porta sem malesuada magna mollis euismod

    - - -
    -
    -
    -
    -

    Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    - - -

    Pellentesque ornare sem

    - -

    Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

    - -

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

    - -

    Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

    - -

    Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

    - -

    Cras mattis consectetur

    - -

    Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

    - -

    Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

    -
    - - -
    - -
    - - - - - - - - - -
    - -
    - -
    -
    -
    -

    Installing Webfonts

    - -

    Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

    - -

    1. Upload your webfonts

    -

    You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

    - -

    2. Include the webfont stylesheet

    -

    A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

    - - - -@font-face{ - font-family: 'MyWebFont'; - src: url('WebFont.eot'); - src: url('WebFont.eot?iefix') format('eot'), - url('WebFont.woff') format('woff'), - url('WebFont.ttf') format('truetype'), - url('WebFont.svg#webfont') format('svg'); -} - - -

    We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

    - <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> - -

    3. Modify your own stylesheet

    -

    To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

    -p { font-family: 'MyWebFont', Arial, sans-serif; } - -

    4. Test

    -

    Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

    -
    - - -
    - -
    - -
    - -
    - - diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot deleted file mode 100644 index 6bbc3cf58cb011a6b4bf3cb1612ce212608f7274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19836 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf deleted file mode 100644 index c537f8382a42986cc5e0d5a06b1460df4f0a5e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38232 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 S)0Gzotc-JM?Hi4}ZTDZPiC;_r diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff deleted file mode 100644 index e231183dce4c7b452afc9e7799586fd285e146f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22660 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 ML;wL%uV=LY00kR;TmS$7 diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png b/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png deleted file mode 100644 index 8dfaed30ad56f7273d757737b7498bd2844d5557..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84913 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 -

    - -
    -
    -
    -
    -
    -
    -

    - Bilan 2014 de l'hébergement -

    -
    -
    -
    -
    - Date - Sat 20 December 2014 - - Tags - Hébergement -
    -

    Déjà une année que j'ai migré l'hébergement de mes services d'un serveur à la -maison vers un hébergeur, en l'occurence OVH et son VPS Classic 2, sur un -container OpenVz dédié.

    -

    Les services hébergés sont à peu près les mêmes avec quelques nouveautés :

    -
    -

    J'ai commencé à utilisé Wallabag depuis quelques -semaines, c'est la pièce qui me manquait dans mon processus de veille, entre le -lien récupéré en vitesse et avant l'ajout du lien dans mes favoris Shaarli s'il -en vaut la peine. Je l'utilise dans le cadre du projet -Framabag mais je prévois d'installer ma propre -instance d'ici peu.

    -
    - -
    - - - - - - - - -

    - - - - - - - - - - - - -
    -
    -
    -
    -
    - -
    - -
    - -
    - - - - - - - - - - - - diff --git a/demo/public/js/markdown.js b/demo/public/js/markdown.js deleted file mode 100644 index 65b04e4..0000000 --- a/demo/public/js/markdown.js +++ /dev/null @@ -1,1740 +0,0 @@ -// Released under MIT license -// Copyright (c) 2009-2010 Dominic Baggott -// Copyright (c) 2009-2010 Ash Berlin -// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) -// Date: 2013-09-15T16:12Z - -(function(expose) { - - - - - var MarkdownHelpers = {}; - - // For Spidermonkey based engines - function mk_block_toSource() { - return "Markdown.mk_block( " + - uneval(this.toString()) + - ", " + - uneval(this.trailing) + - ", " + - uneval(this.lineNumber) + - " )"; - } - - // node - function mk_block_inspect() { - var util = require("util"); - return "Markdown.mk_block( " + - util.inspect(this.toString()) + - ", " + - util.inspect(this.trailing) + - ", " + - util.inspect(this.lineNumber) + - " )"; - - } - - MarkdownHelpers.mk_block = function(block, trail, line) { - // Be helpful for default case in tests. - if ( arguments.length === 1 ) - trail = "\n\n"; - - // We actually need a String object, not a string primitive - /* jshint -W053 */ - var s = new String(block); - s.trailing = trail; - // To make it clear its not just a string - s.inspect = mk_block_inspect; - s.toSource = mk_block_toSource; - - if ( line !== undefined ) - s.lineNumber = line; - - return s; - }; - - - var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - // Don't mess with Array.prototype. Its not friendly - if ( Array.prototype.forEach ) { - MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { - return arr.forEach( cb, thisp ); - }; - } - else { - MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { - for (var i = 0; i < arr.length; i++) - cb.call(thisp || arr, arr[i], i, arr); - }; - } - - MarkdownHelpers.isEmpty = function isEmpty( obj ) { - for ( var key in obj ) { - if ( hasOwnProperty.call( obj, key ) ) - return false; - } - return true; - }; - - MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { - return isArray(jsonml) - && jsonml.length > 1 - && typeof jsonml[ 1 ] === "object" - && !( isArray(jsonml[ 1 ]) ) - ? jsonml[ 1 ] - : undefined; - }; - - - - - /** - * class Markdown - * - * Markdown processing in Javascript done right. We have very particular views - * on what constitutes 'right' which include: - * - * - produces well-formed HTML (this means that em and strong nesting is - * important) - * - * - has an intermediate representation to allow processing of parsed data (We - * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). - * - * - is easily extensible to add new dialects without having to rewrite the - * entire parsing mechanics - * - * - has a good test suite - * - * This implementation fulfills all of these (except that the test suite could - * do with expanding to automatically run all the fixtures from other Markdown - * implementations.) - * - * ##### Intermediate Representation - * - * *TODO* Talk about this :) Its JsonML, but document the node names we use. - * - * [JsonML]: http://jsonml.org/ "JSON Markup Language" - **/ - var Markdown = function(dialect) { - switch (typeof dialect) { - case "undefined": - this.dialect = Markdown.dialects.Gruber; - break; - case "object": - this.dialect = dialect; - break; - default: - if ( dialect in Markdown.dialects ) - this.dialect = Markdown.dialects[dialect]; - else - throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); - break; - } - this.em_state = []; - this.strong_state = []; - this.debug_indent = ""; - }; - - /** - * Markdown.dialects - * - * Namespace of built-in dialects. - **/ - Markdown.dialects = {}; - - - - - // Imported functions - var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, - isArray = MarkdownHelpers.isArray; - - /** - * parse( markdown, [dialect] ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. - **/ - Markdown.parse = function( source, dialect ) { - // dialect will default if undefined - var md = new Markdown( dialect ); - return md.toTree( source ); - }; - - function count_lines( str ) { - var n = 0, - i = -1; - while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) - n++; - return n; - } - - // Internal - split source into rough blocks - Markdown.prototype.split_blocks = function splitBlocks( input ) { - input = input.replace(/(\r\n|\n|\r)/g, "\n"); - // [\s\S] matches _anything_ (newline or space) - // [^] is equivalent but doesn't work in IEs. - var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, - blocks = [], - m; - - var line_no = 1; - - if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { - // skip (but count) leading blank lines - line_no += count_lines( m[0] ); - re.lastIndex = m[0].length; - } - - while ( ( m = re.exec(input) ) !== null ) { - if (m[2] === "\n#") { - m[2] = "\n"; - re.lastIndex--; - } - blocks.push( mk_block( m[1], m[2], line_no ) ); - line_no += count_lines( m[0] ); - } - - return blocks; - }; - - /** - * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] - * - block (String): the block to process - * - next (Array): the following blocks - * - * Process `block` and return an array of JsonML nodes representing `block`. - * - * It does this by asking each block level function in the dialect to process - * the block until one can. Succesful handling is indicated by returning an - * array (with zero or more JsonML nodes), failure by a false value. - * - * Blocks handlers are responsible for calling [[Markdown#processInline]] - * themselves as appropriate. - * - * If the blocks were split incorrectly or adjacent blocks need collapsing you - * can adjust `next` in place using shift/splice etc. - * - * If any of this default behaviour is not right for the dialect, you can - * define a `__call__` method on the dialect that will get invoked to handle - * the block processing. - */ - Markdown.prototype.processBlock = function processBlock( block, next ) { - var cbs = this.dialect.block, - ord = cbs.__order__; - - if ( "__call__" in cbs ) - return cbs.__call__.call(this, block, next); - - for ( var i = 0; i < ord.length; i++ ) { - //D:this.debug( "Testing", ord[i] ); - var res = cbs[ ord[i] ].call( this, block, next ); - if ( res ) { - //D:this.debug(" matched"); - if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) - this.debug(ord[i], "didn't return a proper array"); - //D:this.debug( "" ); - return res; - } - } - - // Uhoh! no match! Should we throw an error? - return []; - }; - - Markdown.prototype.processInline = function processInline( block ) { - return this.dialect.inline.__call__.call( this, String( block ) ); - }; - - /** - * Markdown#toTree( source ) -> JsonML - * - source (String): markdown source to parse - * - * Parse `source` into a JsonML tree representing the markdown document. - **/ - // custom_tree means set this.tree to `custom_tree` and restore old value on return - Markdown.prototype.toTree = function toTree( source, custom_root ) { - var blocks = source instanceof Array ? source : this.split_blocks( source ); - - // Make tree a member variable so its easier to mess with in extensions - var old_tree = this.tree; - try { - this.tree = custom_root || this.tree || [ "markdown" ]; - - blocks_loop: - while ( blocks.length ) { - var b = this.processBlock( blocks.shift(), blocks ); - - // Reference blocks and the like won't return any content - if ( !b.length ) - continue blocks_loop; - - this.tree.push.apply( this.tree, b ); - } - return this.tree; - } - finally { - if ( custom_root ) - this.tree = old_tree; - } - }; - - // Noop by default - Markdown.prototype.debug = function () { - var args = Array.prototype.slice.call( arguments); - args.unshift(this.debug_indent); - if ( typeof print !== "undefined" ) - print.apply( print, args ); - if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) - console.log.apply( null, args ); - }; - - Markdown.prototype.loop_re_over_block = function( re, block, cb ) { - // Dont use /g regexps with this - var m, - b = block.valueOf(); - - while ( b.length && (m = re.exec(b) ) !== null ) { - b = b.substr( m[0].length ); - cb.call(this, m); - } - return b; - }; - - // Build default order from insertion order. - Markdown.buildBlockOrder = function(d) { - var ord = []; - for ( var i in d ) { - if ( i === "__order__" || i === "__call__" ) - continue; - ord.push( i ); - } - d.__order__ = ord; - }; - - // Build patterns for inline matcher - Markdown.buildInlinePatterns = function(d) { - var patterns = []; - - for ( var i in d ) { - // __foo__ is reserved and not a pattern - if ( i.match( /^__.*__$/) ) - continue; - var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) - .replace( /\n/, "\\n" ); - patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); - } - - patterns = patterns.join("|"); - d.__patterns__ = patterns; - //print("patterns:", uneval( patterns ) ); - - var fn = d.__call__; - d.__call__ = function(text, pattern) { - if ( pattern !== undefined ) - return fn.call(this, text, pattern); - else - return fn.call(this, text, patterns); - }; - }; - - - - - var extract_attr = MarkdownHelpers.extract_attr; - - /** - * renderJsonML( jsonml[, options] ) -> String - * - jsonml (Array): JsonML array to render to XML - * - options (Object): options - * - * Converts the given JsonML into well-formed XML. - * - * The options currently understood are: - * - * - root (Boolean): wether or not the root node should be included in the - * output, or just its children. The default `false` is to not include the - * root itself. - */ - Markdown.renderJsonML = function( jsonml, options ) { - options = options || {}; - // include the root element in the rendered output? - options.root = options.root || false; - - var content = []; - - if ( options.root ) { - content.push( render_tree( jsonml ) ); - } - else { - jsonml.shift(); // get rid of the tag - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - jsonml.shift(); // get rid of the attributes - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - } - - return content.join( "\n\n" ); - }; - - - /** - * toHTMLTree( markdown, [dialect] ) -> JsonML - * toHTMLTree( md_tree ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Turn markdown into HTML, represented as a JsonML tree. If a string is given - * to this function, it is first parsed into a markdown tree by calling - * [[parse]]. - **/ - Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { - - // convert string input to an MD tree - if ( typeof input === "string" ) - input = this.parse( input, dialect ); - - // Now convert the MD tree to an HTML tree - - // remove references from the tree - var attrs = extract_attr( input ), - refs = {}; - - if ( attrs && attrs.references ) - refs = attrs.references; - - var html = convert_tree_to_html( input, refs , options ); - merge_text_nodes( html ); - return html; - }; - - /** - * toHTML( markdown, [dialect] ) -> String - * toHTML( md_tree ) -> String - * - markdown (String): markdown string to parse - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Take markdown (either as a string or as a JsonML tree) and run it through - * [[toHTMLTree]] then turn it into a well-formated HTML fragment. - **/ - Markdown.toHTML = function toHTML( source , dialect , options ) { - var input = this.toHTMLTree( source , dialect , options ); - - return this.renderJsonML( input ); - }; - - - function escapeHTML( text ) { - return text.replace( /&/g, "&" ) - .replace( //g, ">" ) - .replace( /"/g, """ ) - .replace( /'/g, "'" ); - } - - function render_tree( jsonml ) { - // basic case - if ( typeof jsonml === "string" ) - return escapeHTML( jsonml ); - - var tag = jsonml.shift(), - attributes = {}, - content = []; - - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - attributes = jsonml.shift(); - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - - var tag_attrs = ""; - for ( var a in attributes ) - tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; - - // be careful about adding whitespace here for inline elements - if ( tag === "img" || tag === "br" || tag === "hr" ) - return "<"+ tag + tag_attrs + "/>"; - else - return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; - } - - function convert_tree_to_html( tree, references, options ) { - var i; - options = options || {}; - - // shallow clone - var jsonml = tree.slice( 0 ); - - if ( typeof options.preprocessTreeNode === "function" ) - jsonml = options.preprocessTreeNode(jsonml, references); - - // Clone attributes if they exist - var attrs = extract_attr( jsonml ); - if ( attrs ) { - jsonml[ 1 ] = {}; - for ( i in attrs ) { - jsonml[ 1 ][ i ] = attrs[ i ]; - } - attrs = jsonml[ 1 ]; - } - - // basic case - if ( typeof jsonml === "string" ) - return jsonml; - - // convert this node - switch ( jsonml[ 0 ] ) { - case "header": - jsonml[ 0 ] = "h" + jsonml[ 1 ].level; - delete jsonml[ 1 ].level; - break; - case "bulletlist": - jsonml[ 0 ] = "ul"; - break; - case "numberlist": - jsonml[ 0 ] = "ol"; - break; - case "listitem": - jsonml[ 0 ] = "li"; - break; - case "para": - jsonml[ 0 ] = "p"; - break; - case "markdown": - jsonml[ 0 ] = "html"; - if ( attrs ) - delete attrs.references; - break; - case "code_block": - jsonml[ 0 ] = "pre"; - i = attrs ? 2 : 1; - var code = [ "code" ]; - code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); - jsonml[ i ] = code; - break; - case "inlinecode": - jsonml[ 0 ] = "code"; - break; - case "img": - jsonml[ 1 ].src = jsonml[ 1 ].href; - delete jsonml[ 1 ].href; - break; - case "linebreak": - jsonml[ 0 ] = "br"; - break; - case "link": - jsonml[ 0 ] = "a"; - break; - case "link_ref": - jsonml[ 0 ] = "a"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.href = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - case "img_ref": - jsonml[ 0 ] = "img"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.src = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - } - - // convert all the children - i = 1; - - // deal with the attribute node, if it exists - if ( attrs ) { - // if there are keys, skip over it - for ( var key in jsonml[ 1 ] ) { - i = 2; - break; - } - // if there aren't, remove it - if ( i === 1 ) - jsonml.splice( i, 1 ); - } - - for ( ; i < jsonml.length; ++i ) { - jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); - } - - return jsonml; - } - - - // merges adjacent text nodes into a single node - function merge_text_nodes( jsonml ) { - // skip the tag name and attribute hash - var i = extract_attr( jsonml ) ? 2 : 1; - - while ( i < jsonml.length ) { - // if it's a string check the next item too - if ( typeof jsonml[ i ] === "string" ) { - if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { - // merge the second string into the first and remove it - jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; - } - else { - ++i; - } - } - // if it's not a string recurse - else { - merge_text_nodes( jsonml[ i ] ); - ++i; - } - } - }; - - - - var DialectHelpers = {}; - DialectHelpers.inline_until_char = function( text, want ) { - var consumed = 0, - nodes = []; - - while ( true ) { - if ( text.charAt( consumed ) === want ) { - // Found the character we were looking for - consumed++; - return [ consumed, nodes ]; - } - - if ( consumed >= text.length ) { - // No closing char found. Abort. - return null; - } - - var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); - consumed += res[ 0 ]; - // Add any returned nodes. - nodes.push.apply( nodes, res.slice( 1 ) ); - } - }; - - // Helper function to make sub-classing a dialect easier - DialectHelpers.subclassDialect = function( d ) { - function Block() {} - Block.prototype = d.block; - function Inline() {} - Inline.prototype = d.inline; - - return { block: new Block(), inline: new Inline() }; - }; - - - - - var forEach = MarkdownHelpers.forEach, - extract_attr = MarkdownHelpers.extract_attr, - mk_block = MarkdownHelpers.mk_block, - isEmpty = MarkdownHelpers.isEmpty, - inline_until_char = DialectHelpers.inline_until_char; - - /** - * Gruber dialect - * - * The default dialect that follows the rules set out by John Gruber's - * markdown.pl as closely as possible. Well actually we follow the behaviour of - * that script which in some places is not exactly what the syntax web page - * says. - **/ - var Gruber = { - block: { - atxHeader: function atxHeader( block, next ) { - var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var header = [ "header", { level: m[ 1 ].length } ]; - Array.prototype.push.apply(header, this.processInline(m[ 2 ])); - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - setextHeader: function setextHeader( block, next ) { - var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var level = ( m[ 2 ] === "=" ) ? 1 : 2, - header = [ "header", { level : level }, m[ 1 ] ]; - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - code: function code( block, next ) { - // | Foo - // |bar - // should be a code block followed by a paragraph. Fun - // - // There might also be adjacent code block to merge. - - var ret = [], - re = /^(?: {0,3}\t| {4})(.*)\n?/; - - // 4 spaces + content - if ( !block.match( re ) ) - return undefined; - - block_search: - do { - // Now pull out the rest of the lines - var b = this.loop_re_over_block( - re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); - - if ( b.length ) { - // Case alluded to in first comment. push it back on as a new block - next.unshift( mk_block(b, block.trailing) ); - break block_search; - } - else if ( next.length ) { - // Check the next block - it might be code too - if ( !next[0].match( re ) ) - break block_search; - - // Pull how how many blanks lines follow - minus two to account for .join - ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); - - block = next.shift(); - } - else { - break block_search; - } - } while ( true ); - - return [ [ "code_block", ret.join("\n") ] ]; - }, - - horizRule: function horizRule( block, next ) { - // this needs to find any hr in the block to handle abutting blocks - var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); - - if ( !m ) - return undefined; - - var jsonml = [ [ "hr" ] ]; - - // if there's a leading abutting block, process it - if ( m[ 1 ] ) { - var contained = mk_block( m[ 1 ], "", block.lineNumber ); - jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); - } - - // if there's a trailing abutting block, stick it into next - if ( m[ 3 ] ) - next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); - - return jsonml; - }, - - // There are two types of lists. Tight and loose. Tight lists have no whitespace - // between the items (and result in text just in the

    9?uG;6JXDPtu17tlup#cGgk~?1Nc5Z5wH} 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­ml+D=R+taKLSXsz@g$ zc-y&+;wwh5i{McJL6zP*k`LyNw|&&uSmDunoh~+u4vIZjNCKR)f$9vV*OaK*$g5RPl?qg;Nj9Y%UK)C_gvbY^ywCMC@vwl!og15YF~wBxik9}h zKv5pq{%7FpQ4q6=52rL7TMn(b6km|}+S&_7CFUEpdIgdjZKFV}Ks~7=H#YAfGDy~( zvmeLvt&@Djc_uh22|9m5YZ^<>0wV{nJJbd>g9Z0nzuyD&nMTg=W-OA|hqZ2Jh^9uY zhycKP;0$JuLT+%Y^he&lP#g5Kf7o+4`W6~bA@i|x=e#p&EETjY2}?qb#cg|^hk2W>DV=Qdu&!`3 z4V*2K_nXOx%keywv+Yce>FazNm;IsUn=3Q880CWhoGdYm`fdQc0+{l9 z8_F0qe{Y2bB|7^Ttq)GW*>Uf1PQr~ay6JjEZDGZGwD@SUN6K(l?)VT_o8(mV$IB#g z@5-7qcdX^TsHjZ3d_6?e_j`WcYh_Z)V|?>zZdgrv6>;>ajati=FDcB_*2Y3JUi@^n z(^;68!6f0wsdsVr(4VcNSe20V`A6iiV~BGodJGV@dXgi1$(pUC(D2{;q%m@cnyknH zc-Jl0lp7&s6GNHrO;f<{pxfgLb{rv^CJLP%7B7i%4iKGk;A6)k3`dv@_yW$`8zVQ( z!)7BWe4kbfFy?*oeE#$Wzu^DEvCJ|0;kw;V&1@pSlQbZR0Nta9xiMSa{!xp}p=K!G3Eo zU3LSSABJb38TXA!QoZ zG7@x>(JRPxO;A26Zkc?pAj|Fv2zUJaUD>1aU$NQ4cg8;7^CPev1OIQ6O^zqIl*92V zu42K|wd={RH}p9VPf(amMc5JCxLStZ>!oCvrV7VQ~aLZ0X-1+8USnw}w9BF9krdEnfGw^>4?i1BKb-Ecr z2N^5fH?+H)LJBWR%WSudYO`%cjcrMpf0#R1}uwHGKn{3p>MwHRqE3fe< zM8OSo|DZto*Thds&|0(Ck#pFiA7!SUG*qzT3vP{y;d8;K(8G{cZ@;&Jd+wZbu>i0S z+p`OJ5H%@}e_3Uf7y5WS3*AR#bPNGc49OjC4MMOnUXET@4?$oDgWV{W?4dv)eWPso zAq40pNM>$mSsT!S_?{ih7U;nt<3h6mIRmY&07wFpnS;w{Cn9`LU)5Mh>?(Bve}n2` zTfuji2GfCJSEB({(sc)6D;rw6>*1g(Rpy+nK8_bGklr{O24x^*6}Lg7F-&F(a0e1td(X8cs1jcbK&+zbN@sO?JH>w;|&7r zO8=IvLAV*r;19v>J-jn84Mt@1i4P!W4fdV|x$_PvpG)pH>|;Hj4|uL%w7MXDtGRd~ z^k(=+ePnEmP_{Q-X2e04dPbeY~ z0QVm?J&``+-?+D3I)SR|0a}y520&Qfe{_o?_2%bY@q$O|Xxu(W4VVI5pZT|7?nri) zVA`)9b0*YnFA&i%|Dbj)_CO=x+{8Vh4Y*h(^fv#8ID`?qz=}&q<$R|CUn{@}_aW%)#+H55;+yTo5iUPEIa99v*Ip0H1&WKOaB8ppck| zppd8#Kfj2Sh^V-Pq@<*PF!YFSzGNr~T&fWU`$IJtPaxOgQ5`2{8Z&tks=L?M6; zU;_r71UN)NU{Mge3y?j8wH~joV!@_SzL`Fs5yZ<0QA@N~S`jd>ztn8=H z@P$RiFH1_x%3syi)e{;To0{JeJG;7jdO!5_kBp9uPfSitlPQZ!%U@Pj*S@aPzyJ8T z&Ddf7+WpN10>J;qI^6#a_CL5p54kuvIl-Jfzqvph5r+#b%E=|K!7XNJ%i|Fuep2%; zM8YWTMa_F&1uZ+8#_d{T<%C`$C-X#XPn-+{&bzsUXz?7z9lfB+bDI6SZ@fB+64 zLxiJ@vbwF^$^3l}=5!(E#E5+Ufmi#u+73Tlb1vbT&b6THNXVnK<{`^FAVu(3Qb;LO zrA0APG`Upi$m@n^1MKT;<|8`Hi0Mpe)~<|R?hHC|SwKVWSCx`nW{NiJ)St?q0CPDG z(=J!7gCdq3le}m|p3Rqe=m|d*^X5c{5*25j%^FtnHfWJ zY}>My2Ig=xm>#{*ySd@rn2#9Nef4AJjiWW2cK!Z+rMAVXf%I4^ZV49?1vPA_p%EVL zsu?jxd_P(S>TB=xQ<6A%1~x;6 z+;x`of#qDst89R8rpIW`mkr!UqHY{DhIK-+Z|cW!h*@E9m?bfMp3`jk8o|~zi7NB+ z?75T8_Ug<$cn8&rj_4dFjIm&>ktF%7ls(J1%L=o`$8`1uqYH>zGc#tBO7Sz@w;SFl z&<`pL`+Xinz1O^Gp2q!8IVR#A=j32E(MoO2Xqj7j8bIk6Md`U?GyCfoUetmx6N{xGvQr5ypGaF1rp&WW1xu_HR81}7l0ZD3=W(+oa zF`Y|@!hInX?^5qgduAE#KCjXz=AQ8=B~^PZd=9=>e%dir*+n>IV+!G0a9=l~i)UQB zKxDcXTR0fjHwbox4Rf+x6u5xB5Oc ziHBbIu-i*2m;3gdD*)@R= zs&RjPA%g*Z7#K2MRRg}>fQ!xd!5t&A(h_@+e}Yd0tRTf> zK1>{QXiv{#$>W_-t&O!FWmDZcIIj>f(fSgz+sIDsAFZ})9Q-vNR31G) zSKx}Ox0s`TN?v?`wy`U&SbB|h=+Js~kZ&LwxkbtDxw7;MU2#lh)GKL`G4IZp3|x^< zxIt+Yo|k=QXyd&1uAqE>DCC+Ou_Ga?fNx(q;5mB!np=hCNMGo!*_N6UV00+E7}hFXzEzG_2HoXEomkr?FO4RlzZ z&#SAMPh1+=K)g=sgY)@1^ESjG|W9yG4zAnxG|;R zXryP)v4uo^;l&LrfsHFc!Qa9!$hjG-zIlQ3vQxrkWRQ!&q+utk#e$T&s$P0_96VlQ z#W@nS8auh#p@4W0-E4w=FEX%3tWfE(P|-dUkx8vwP;T98FO|fod9C+#x40Ftfw)NS zI{LjuQZ^0T<*LXU{8WV^j@qPLh}bi_^wu}t+ymX-pY;;#4Vvi)E(e!3F&7xoBl;IU z^JAl9B`_%ZT`_dOYM3;0wJAcg$L>d`f zQ=LnhSEtOjNGIYj=RBZ)oVGsO(NYZ`ZGEz_IUS|Y2))ydD&^sApuYP1iQ%W}cIo}d z@m3jqt19Lrlbp`mZ|h;z*Oa>6SOx{1#8#D+`q^6xMM-{bW>m{DbtpLNp{{x534P?E z%hm3A)nvoskFWe?EKCB2E$N9xNs6LBZq8(E>VQ`iBscVierX@r-mOML<4Y5Z)NWXv zDhS&){qpp~p6>U1r~sqm!}xo#ZE9>F`e#MdY;VrKC2cFGV$RLg+12%iQwbjL{dIK0 z1V7fR&>AF#$V+RTbR-MUS;oCgd`Vky91UBpqpf6e?m$H@*1BnSo;&L-*y0e^&%L1W zm;!D8G4*6Ur+MRA@Iz150DU20`!o$|YaW(!NAC?PU&>(M%V5#G|2{I6X_2K4e)Lhr z;@Y}C3A*E)L}0Xyg%iG!-4KKip;1>~BYDp?I}>&{Mr(#A3~H7H`8(^1b*l)aUu&DV zJX_wHJ&n65bR?xTwK5=gu{mdI8_U0E{&!dl)@Q(O{3jK)v_ph7;p!W{d0H(fCJqF@ zQ_Vr=Y;iCB@=SPh^tN|0<7mF}lo4s9(KTu7Qp~eQuTeMy|FbJtTqWYKHxu$d{tgb@ zy1vmq@9;Rr9lyGr%IhyqUchE|liTj~NoOF|zNKmK@T?@}g15+J%>GqNx<7MbeU)-y zmYh{7a6THdBlR|*tro(lf6@AbF#a+#UPsTdz`_+u>qx+$mpY_ok)LO*w5;m>x#A2r z@J=yXbVzQAN_%mmtGgiNeemN>lA+|D5Sqdg7ky|mp!Jv4=A^3Ex4gZ+7}Jl7bEe1Q(ML*e}UtzZ1ool2dgxs=dnPr{3n`t6FT0sw6MI1K&}J z*qhuA*9|F!5YP0%{7QP4)5}(smB$idn~DZ^B%x2+$D$rlou?yowpOlm%sML3#V*3b zG=;a~>uxCYy1EIbGC{G#x2b2N`F`p3E^9DwU+W;h7&^kVtg`c>Thb7FKP&VvUB`r#^oDfv^s6u zeI*VyQ13=oChmNyW;}2Iv>BTO%W=#^@SYTC`q{@bg~Cnt7J!|Jr0^mGL>!qEb&bV$ zf072bmi@Rk#;_xjShm4*rx@kUu)4vjHuXTHIFHQ!FdI1M)-mcy_f1u zgQ&(^?-cQV(peg{=??CnWq+*)RFBlRTo5Aa!3`#0dVl?BIBkC>&f0u8-CVg`5l^pv zfRM=Pij~+mzOHI{EU2txmlTjzds9A%d@}iYTD3W^7Se>PyM4kdo#um@1rpo;sCQP|7cBx=~bTllzr%XY*SflH=}6K&3~^&Z8bGk Hi#`1x$O7_n diff --git a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg b/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg deleted file mode 100644 index 8a23176c70ab449fcdd43b5da799197a945563ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2637 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 diff --git a/demo/public/img/framasoft.png b/demo/public/img/framasoft.png deleted file mode 100644 index 71e74c308106e6833c9a841ec4edbf07f7ecccfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19259 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 diff --git a/demo/public/img/id-150.jpg b/demo/public/img/id-150.jpg deleted file mode 100644 index ebc872a5c1180aceda7936ccfb7361ac9a4f68a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19264 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 diff --git a/demo/public/img/license.png b/demo/public/img/license.png deleted file mode 100644 index 0f2a0f10722d3fabffe8af5d5eed09866e25909a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5460 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 Ng2}4L)JmC!{ST_+RpI~u diff --git a/demo/public/index.html b/demo/public/index.html deleted file mode 100644 index 7e016ef..0000000 --- a/demo/public/index.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - Bilan 2014 de l'hébergement - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  • ) and loose lists, - // which have an empty line between list items, resulting in (one or more) - // paragraphs inside the
  • . - // - // There are all sorts weird edge cases about the original markdown.pl's - // handling of lists: - // - // * Nested lists are supposed to be indented by four chars per level. But - // if they aren't, you can get a nested list by indenting by less than - // four so long as the indent doesn't match an indent of an existing list - // item in the 'nest stack'. - // - // * The type of the list (bullet or number) is controlled just by the - // first item at the indent. Subsequent changes are ignored unless they - // are for nested lists - // - lists: (function( ) { - // Use a closure to hide a few variables. - var any_list = "[*+-]|\\d+\\.", - bullet_list = /[*+-]/, - // Capture leading indent as it matters for determining nested lists. - is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), - indent_re = "(?: {0,3}\\t| {4})"; - - // TODO: Cache this regexp for certain depths. - // Create a regexp suitable for matching an li for a given stack depth - function regex_for_depth( depth ) { - - return new RegExp( - // m[1] = indent, m[2] = list_type - "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + - // m[3] = cont - "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" - ); - } - function expand_tab( input ) { - return input.replace( / {0,3}\t/g, " " ); - } - - // Add inline content `inline` to `li`. inline comes from processInline - // so is an array of content - function add(li, loose, inline, nl) { - if ( loose ) { - li.push( [ "para" ].concat(inline) ); - return; - } - // Hmmm, should this be any block level element or just paras? - var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" - ? li[li.length -1] - : li; - - // If there is already some content in this list, add the new line in - if ( nl && li.length > 1 ) - inline.unshift(nl); - - for ( var i = 0; i < inline.length; i++ ) { - var what = inline[i], - is_str = typeof what === "string"; - if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) - add_to[ add_to.length-1 ] += what; - else - add_to.push( what ); - } - } - - // contained means have an indent greater than the current one. On - // *every* line in the block - function get_contained_blocks( depth, blocks ) { - - var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), - replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), - ret = []; - - while ( blocks.length > 0 ) { - if ( re.exec( blocks[0] ) ) { - var b = blocks.shift(), - // Now remove that indent - x = b.replace( replace, ""); - - ret.push( mk_block( x, b.trailing, b.lineNumber ) ); - } - else - break; - } - return ret; - } - - // passed to stack.forEach to turn list items up the stack into paras - function paragraphify(s, i, stack) { - var list = s.list; - var last_li = list[list.length-1]; - - if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) - return; - if ( i + 1 === stack.length ) { - // Last stack frame - // Keep the same array, but replace the contents - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); - } - else { - var sublist = last_li.pop(); - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); - } - } - - // The matcher function - return function( block, next ) { - var m = block.match( is_list_re ); - if ( !m ) - return undefined; - - function make_list( m ) { - var list = bullet_list.exec( m[2] ) - ? ["bulletlist"] - : ["numberlist"]; - - stack.push( { list: list, indent: m[1] } ); - return list; - } - - - var stack = [], // Stack of lists for nesting. - list = make_list( m ), - last_li, - loose = false, - ret = [ stack[0].list ], - i; - - // Loop to search over block looking for inner block elements and loose lists - loose_search: - while ( true ) { - // Split into lines preserving new lines at end of line - var lines = block.split( /(?=\n)/ ); - - // We have to grab all lines for a li and call processInline on them - // once as there are some inline things that can span lines. - var li_accumulate = "", nl = ""; - - // Loop over the lines in this block looking for tight lists. - tight_search: - for ( var line_no = 0; line_no < lines.length; line_no++ ) { - nl = ""; - var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); - - - // TODO: really should cache this - var line_re = regex_for_depth( stack.length ); - - m = l.match( line_re ); - //print( "line:", uneval(l), "\nline match:", uneval(m) ); - - // We have a list item - if ( m[1] !== undefined ) { - // Process the previous list item, if any - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - m[1] = expand_tab( m[1] ); - var wanted_depth = Math.floor(m[1].length/4)+1; - //print( "want:", wanted_depth, "stack:", stack.length); - if ( wanted_depth > stack.length ) { - // Deep enough for a nested list outright - //print ( "new nested list" ); - list = make_list( m ); - last_li.push( list ); - last_li = list[1] = [ "listitem" ]; - } - else { - // We aren't deep enough to be strictly a new level. This is - // where Md.pl goes nuts. If the indent matches a level in the - // stack, put it there, else put it one deeper then the - // wanted_depth deserves. - var found = false; - for ( i = 0; i < stack.length; i++ ) { - if ( stack[ i ].indent !== m[1] ) - continue; - - list = stack[ i ].list; - stack.splice( i+1, stack.length - (i+1) ); - found = true; - break; - } - - if (!found) { - //print("not found. l:", uneval(l)); - wanted_depth++; - if ( wanted_depth <= stack.length ) { - stack.splice(wanted_depth, stack.length - wanted_depth); - //print("Desired depth now", wanted_depth, "stack:", stack.length); - list = stack[wanted_depth-1].list; - //print("list:", uneval(list) ); - } - else { - //print ("made new stack for messy indent"); - list = make_list(m); - last_li.push(list); - } - } - - //print( uneval(list), "last", list === stack[stack.length-1].list ); - last_li = [ "listitem" ]; - list.push(last_li); - } // end depth of shenegains - nl = ""; - } - - // Add content - if ( l.length > m[0].length ) - li_accumulate += nl + l.substr( m[0].length ); - } // tight_search - - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - // Look at the next block - we might have a loose list. Or an extra - // paragraph for the current li - var contained = get_contained_blocks( stack.length, next ); - - // Deal with code blocks or properly nested lists - if ( contained.length > 0 ) { - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - last_li.push.apply( last_li, this.toTree( contained, [] ) ); - } - - var next_block = next[0] && next[0].valueOf() || ""; - - if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { - block = next.shift(); - - // Check for an HR following a list: features/lists/hr_abutting - var hr = this.dialect.block.horizRule( block, next ); - - if ( hr ) { - ret.push.apply(ret, hr); - break; - } - - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - loose = true; - continue loose_search; - } - break; - } // loose_search - - return ret; - }; - })(), - - blockquote: function blockquote( block, next ) { - if ( !block.match( /^>/m ) ) - return undefined; - - var jsonml = []; - - // separate out the leading abutting block, if any. I.e. in this case: - // - // a - // > b - // - if ( block[ 0 ] !== ">" ) { - var lines = block.split( /\n/ ), - prev = [], - line_no = block.lineNumber; - - // keep shifting lines until you find a crotchet - while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { - prev.push( lines.shift() ); - line_no++; - } - - var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); - jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); - // reassemble new block of just block quotes! - block = mk_block( lines.join( "\n" ), block.trailing, line_no ); - } - - - // if the next block is also a blockquote merge it in - while ( next.length && next[ 0 ][ 0 ] === ">" ) { - var b = next.shift(); - block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); - } - - // Strip off the leading "> " and re-process as a block. - var input = block.replace( /^> ?/gm, "" ), - old_tree = this.tree, - processedBlock = this.toTree( input, [ "blockquote" ] ), - attr = extract_attr( processedBlock ); - - // If any link references were found get rid of them - if ( attr && attr.references ) { - delete attr.references; - // And then remove the attribute object if it's empty - if ( isEmpty( attr ) ) - processedBlock.splice( 1, 1 ); - } - - jsonml.push( processedBlock ); - return jsonml; - }, - - referenceDefn: function referenceDefn( block, next) { - var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; - // interesting matches are [ , ref_id, url, , title, title ] - - if ( !block.match(re) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var attrs = extract_attr( this.tree ); - - // make a references hash if it doesn't exist - if ( attrs.references === undefined ) - attrs.references = {}; - - var b = this.loop_re_over_block(re, block, function( m ) { - - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - var ref = attrs.references[ m[1].toLowerCase() ] = { - href: m[2] - }; - - if ( m[4] !== undefined ) - ref.title = m[4]; - else if ( m[5] !== undefined ) - ref.title = m[5]; - - } ); - - if ( b.length ) - next.unshift( mk_block( b, block.trailing ) ); - - return []; - }, - - para: function para( block ) { - // everything's a para! - return [ ["para"].concat( this.processInline( block ) ) ]; - } - }, - - inline: { - - __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { - var m, - res; - - patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; - var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); - - m = re.exec( text ); - if (!m) { - // Just boring text - return [ text.length, text ]; - } - else if ( m[1] ) { - // Some un-interesting text matched. Return that first - return [ m[1].length, m[1] ]; - } - - var res; - if ( m[2] in this.dialect.inline ) { - res = this.dialect.inline[ m[2] ].call( - this, - text.substr( m.index ), m, previous_nodes || [] ); - } - // Default for now to make dev easier. just slurp special and output it. - res = res || [ m[2].length, m[2] ]; - return res; - }, - - __call__: function inline( text, patterns ) { - - var out = [], - res; - - function add(x) { - //D:self.debug(" adding output", uneval(x)); - if ( typeof x === "string" && typeof out[out.length-1] === "string" ) - out[ out.length-1 ] += x; - else - out.push(x); - } - - while ( text.length > 0 ) { - res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); - text = text.substr( res.shift() ); - forEach(res, add ); - } - - return out; - }, - - // These characters are intersting elsewhere, so have rules for them so that - // chunks of plain text blocks don't include them - "]": function () {}, - "}": function () {}, - - __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, - - "\\": function escaped( text ) { - // [ length of input processed, node/children to add... ] - // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! - if ( this.dialect.inline.__escape__.exec( text ) ) - return [ 2, text.charAt( 1 ) ]; - else - // Not an esacpe - return [ 1, "\\" ]; - }, - - "![": function image( text ) { - - // Unlike images, alt text is plain text only. no other elements are - // allowed in there - - // ![Alt text](/path/to/img.jpg "Optional title") - // 1 2 3 4 <--- captures - var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); - - if ( m ) { - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; - - var attrs = { alt: m[1], href: m[2] || "" }; - if ( m[4] !== undefined) - attrs.title = m[4]; - - return [ m[0].length, [ "img", attrs ] ]; - } - - // ![Alt text][id] - m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); - - if ( m ) { - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion - return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; - } - - // Just consume the '![' - return [ 2, "![" ]; - }, - - "[": function link( text ) { - - var orig = String(text); - // Inline content is possible inside `link text` - var res = inline_until_char.call( this, text.substr(1), "]" ); - - // No closing ']' found. Just consume the [ - if ( !res ) - return [ 1, "[" ]; - - var consumed = 1 + res[ 0 ], - children = res[ 1 ], - link, - attrs; - - // At this point the first [...] has been parsed. See what follows to find - // out which kind of link we are (reference or direct url) - text = text.substr( consumed ); - - // [link text](/path/to/img.jpg "Optional title") - // 1 2 3 <--- captures - // This will capture up to the last paren in the block. We then pull - // back based on if there a matching ones in the url - // ([here](/url/(test)) - // The parens have to be balanced - var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); - if ( m ) { - var url = m[1]; - consumed += m[0].length; - - if ( url && url[0] === "<" && url[url.length-1] === ">" ) - url = url.substring( 1, url.length - 1 ); - - // If there is a title we don't have to worry about parens in the url - if ( !m[3] ) { - var open_parens = 1; // One open that isn't in the capture - for ( var len = 0; len < url.length; len++ ) { - switch ( url[len] ) { - case "(": - open_parens++; - break; - case ")": - if ( --open_parens === 0) { - consumed -= url.length - len; - url = url.substring(0, len); - } - break; - } - } - } - - // Process escapes only - url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; - - attrs = { href: url || "" }; - if ( m[3] !== undefined) - attrs.title = m[3]; - - link = [ "link", attrs ].concat( children ); - return [ consumed, link ]; - } - - // [Alt text][id] - // [Alt text] [id] - m = text.match( /^\s*\[(.*?)\]/ ); - - if ( m ) { - - consumed += m[ 0 ].length; - - // [links][] uses links as its reference - attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; - - link = [ "link_ref", attrs ].concat( children ); - - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion. - // Store the original so that conversion can revert if the ref isn't found. - return [ consumed, link ]; - } - - // [id] - // Only if id is plain (no formatting.) - if ( children.length === 1 && typeof children[0] === "string" ) { - - attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; - link = [ "link_ref", attrs, children[0] ]; - return [ consumed, link ]; - } - - // Just consume the "[" - return [ 1, "[" ]; - }, - - - "<": function autoLink( text ) { - var m; - - if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { - if ( m[3] ) - return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; - else if ( m[2] === "mailto" ) - return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; - else - return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; - } - - return [ 1, "<" ]; - }, - - "`": function inlineCode( text ) { - // Inline code block. as many backticks as you like to start it - // Always skip over the opening ticks. - var m = text.match( /(`+)(([\s\S]*?)\1)/ ); - - if ( m && m[2] ) - return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; - else { - // TODO: No matching end code found - warn! - return [ 1, "`" ]; - } - }, - - " \n": function lineBreak() { - return [ 3, [ "linebreak" ] ]; - } - - } - }; - - // Meta Helper/generator method for em and strong handling - function strong_em( tag, md ) { - - var state_slot = tag + "_state", - other_slot = tag === "strong" ? "em_state" : "strong_state"; - - function CloseTag(len) { - this.len_after = len; - this.name = "close_" + md; - } - - return function ( text ) { - - if ( this[state_slot][0] === md ) { - // Most recent em is of this type - //D:this.debug("closing", md); - this[state_slot].shift(); - - // "Consume" everything to go back to the recrusion in the else-block below - return[ text.length, new CloseTag(text.length-md.length) ]; - } - else { - // Store a clone of the em/strong states - var other = this[other_slot].slice(), - state = this[state_slot].slice(); - - this[state_slot].unshift(md); - - //D:this.debug_indent += " "; - - // Recurse - var res = this.processInline( text.substr( md.length ) ); - //D:this.debug_indent = this.debug_indent.substr(2); - - var last = res[res.length - 1]; - - //D:this.debug("processInline from", tag + ": ", uneval( res ) ); - - var check = this[state_slot].shift(); - if ( last instanceof CloseTag ) { - res.pop(); - // We matched! Huzzah. - var consumed = text.length - last.len_after; - return [ consumed, [ tag ].concat(res) ]; - } - else { - // Restore the state of the other kind. We might have mistakenly closed it. - this[other_slot] = other; - this[state_slot] = state; - - // We can't reuse the processed result as it could have wrong parsing contexts in it. - return [ md.length, md ]; - } - } - }; // End returned function - } - - Gruber.inline["**"] = strong_em("strong", "**"); - Gruber.inline["__"] = strong_em("strong", "__"); - Gruber.inline["*"] = strong_em("em", "*"); - Gruber.inline["_"] = strong_em("em", "_"); - - Markdown.dialects.Gruber = Gruber; - Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); - - - - var Maruku = DialectHelpers.subclassDialect( Gruber ), - extract_attr = MarkdownHelpers.extract_attr, - forEach = MarkdownHelpers.forEach; - - Maruku.processMetaHash = function processMetaHash( meta_string ) { - var meta = split_meta_hash( meta_string ), - attr = {}; - - for ( var i = 0; i < meta.length; ++i ) { - // id: #foo - if ( /^#/.test( meta[ i ] ) ) - attr.id = meta[ i ].substring( 1 ); - // class: .foo - else if ( /^\./.test( meta[ i ] ) ) { - // if class already exists, append the new one - if ( attr["class"] ) - attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); - else - attr["class"] = meta[ i ].substring( 1 ); - } - // attribute: foo=bar - else if ( /\=/.test( meta[ i ] ) ) { - var s = meta[ i ].split( /\=/ ); - attr[ s[ 0 ] ] = s[ 1 ]; - } - } - - return attr; - }; - - function split_meta_hash( meta_string ) { - var meta = meta_string.split( "" ), - parts = [ "" ], - in_quotes = false; - - while ( meta.length ) { - var letter = meta.shift(); - switch ( letter ) { - case " " : - // if we're in a quoted section, keep it - if ( in_quotes ) - parts[ parts.length - 1 ] += letter; - // otherwise make a new part - else - parts.push( "" ); - break; - case "'" : - case '"' : - // reverse the quotes and move straight on - in_quotes = !in_quotes; - break; - case "\\" : - // shift off the next letter to be used straight away. - // it was escaped so we'll keep it whatever it is - letter = meta.shift(); - /* falls through */ - default : - parts[ parts.length - 1 ] += letter; - break; - } - } - - return parts; - } - - Maruku.block.document_meta = function document_meta( block ) { - // we're only interested in the first block - if ( block.lineNumber > 1 ) - return undefined; - - // document_meta blocks consist of one or more lines of `Key: Value\n` - if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var pairs = block.split( /\n/ ); - for ( var p in pairs ) { - var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), - key = m[ 1 ].toLowerCase(), - value = m[ 2 ]; - - this.tree[ 1 ][ key ] = value; - } - - // document_meta produces no content! - return []; - }; - - Maruku.block.block_meta = function block_meta( block ) { - // check if the last line of the block is an meta hash - var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); - if ( !m ) - return undefined; - - // process the meta hash - var attr = this.dialect.processMetaHash( m[ 2 ] ), - hash; - - // if we matched ^ then we need to apply meta to the previous block - if ( m[ 1 ] === "" ) { - var node = this.tree[ this.tree.length - 1 ]; - hash = extract_attr( node ); - - // if the node is a string (rather than JsonML), bail - if ( typeof node === "string" ) - return undefined; - - // create the attribute hash if it doesn't exist - if ( !hash ) { - hash = {}; - node.splice( 1, 0, hash ); - } - - // add the attributes in - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - // return nothing so the meta hash is removed - return []; - } - - // pull the meta hash off the block and process what's left - var b = block.replace( /\n.*$/, "" ), - result = this.processBlock( b, [] ); - - // get or make the attributes hash - hash = extract_attr( result[ 0 ] ); - if ( !hash ) { - hash = {}; - result[ 0 ].splice( 1, 0, hash ); - } - - // attach the attributes to the block - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - return result; - }; - - Maruku.block.definition_list = function definition_list( block, next ) { - // one or more terms followed by one or more definitions, in a single block - var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, - list = [ "dl" ], - i, m; - - // see if we're dealing with a tight or loose block - if ( ( m = block.match( tight ) ) ) { - // pull subsequent tight DL blocks out of `next` - var blocks = [ block ]; - while ( next.length && tight.exec( next[ 0 ] ) ) - blocks.push( next.shift() ); - - for ( var b = 0; b < blocks.length; ++b ) { - var m = blocks[ b ].match( tight ), - terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), - defns = m[ 2 ].split( /\n:\s+/ ); - - // print( uneval( m ) ); - - for ( i = 0; i < terms.length; ++i ) - list.push( [ "dt", terms[ i ] ] ); - - for ( i = 0; i < defns.length; ++i ) { - // run inline processing over the definition - list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); - } - } - } - else { - return undefined; - } - - return [ list ]; - }; - - // splits on unescaped instances of @ch. If @ch is not a character the result - // can be unpredictable - - Maruku.block.table = function table ( block ) { - - var _split_on_unescaped = function( s, ch ) { - ch = ch || '\\s'; - if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) - ch = '\\' + ch; - var res = [ ], - r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), - m; - while ( ( m = s.match( r ) ) ) { - res.push( m[1] ); - s = m[2]; - } - res.push(s); - return res; - }; - - var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, - // find at least an unescaped pipe in each line - no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, - i, - m; - if ( ( m = block.match( leading_pipe ) ) ) { - // remove leading pipes in contents - // (header and horizontal rule already have the leading pipe left out) - m[3] = m[3].replace(/^\s*\|/gm, ''); - } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { - return undefined; - } - - var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; - - // remove trailing pipes, then split on pipes - // (no escaped pipes are allowed in horizontal rule) - m[2] = m[2].replace(/\|\s*$/, '').split('|'); - - // process alignment - var html_attrs = [ ]; - forEach (m[2], function (s) { - if (s.match(/^\s*-+:\s*$/)) - html_attrs.push({align: "right"}); - else if (s.match(/^\s*:-+\s*$/)) - html_attrs.push({align: "left"}); - else if (s.match(/^\s*:-+:\s*$/)) - html_attrs.push({align: "center"}); - else - html_attrs.push({}); - }); - - // now for the header, avoid escaped pipes - m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); - for (i = 0; i < m[1].length; i++) { - table[1][1].push(['th', html_attrs[i] || {}].concat( - this.processInline(m[1][i].trim()))); - } - - // now for body contents - forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { - var html_row = ['tr']; - row = _split_on_unescaped(row, '|'); - for (i = 0; i < row.length; i++) - html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); - table[2].push(html_row); - }, this); - - return [table]; - }; - - Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { - if ( !out.length ) - return [ 2, "{:" ]; - - // get the preceeding element - var before = out[ out.length - 1 ]; - - if ( typeof before === "string" ) - return [ 2, "{:" ]; - - // match a meta hash - var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); - - // no match, false alarm - if ( !m ) - return [ 2, "{:" ]; - - // attach the attributes to the preceeding element - var meta = this.dialect.processMetaHash( m[ 1 ] ), - attr = extract_attr( before ); - - if ( !attr ) { - attr = {}; - before.splice( 1, 0, attr ); - } - - for ( var k in meta ) - attr[ k ] = meta[ k ]; - - // cut out the string and replace it with nothing - return [ m[ 0 ].length, "" ]; - }; - - - Markdown.dialects.Maruku = Maruku; - Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; - Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); - - -// Include all our depndencies and; - expose.Markdown = Markdown; - expose.parse = Markdown.parse; - expose.toHTML = Markdown.toHTML; - expose.toHTMLTree = Markdown.toHTMLTree; - expose.renderJsonML = Markdown.renderJsonML; - -})(function() { - window.markdown = {}; - return window.markdown; -}()); diff --git a/demo/public/js/mustache.js b/demo/public/js/mustache.js deleted file mode 100644 index 18d92a5..0000000 --- a/demo/public/js/mustache.js +++ /dev/null @@ -1,586 +0,0 @@ -/*! - * mustache.js - Logic-less {{mustache}} templates with JavaScript - * http://github.com/janl/mustache.js - */ - -/*global define: false*/ - -(function (global, factory) { - if (typeof exports === "object" && exports) { - factory(exports); // CommonJS - } else if (typeof define === "function" && define.amd) { - define(['exports'], factory); // AMD - } else { - factory(global.Mustache = {}); // - - - diff --git a/requirements.txt b/requirements.txt index 992beb2..0024e5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,29 @@ +APScheduler==3.5.3 +astroid==2.0.4 attrs==17.4.0 chardet==3.0.4 click==6.7 clize==4.0.3 docutils==0.14 Flask==0.12.2 +Flask-APScheduler==1.10.1 Flask-Cors==3.0.3 +isort==4.3.4 itsdangerous==0.24 Jinja2==2.10 jsonschema==2.6.0 +lazy-object-proxy==1.3.1 Markdown==2.6.11 MarkupSafe==1.0 +mccabe==0.6.1 od==1.0 peewee==2.10.2 -pika==0.11.2 +pylint==2.1.1 PyRSS2Gen==1.1 +python-dateutil==2.7.3 +pytz==2018.5 sigtools==2.0.1 six==1.11.0 +tzlocal==1.5.1 Werkzeug==0.14.1 +wrapt==1.10.11 From ed2a28410294c9772cab060b4139fa4b453e68a5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 2 Sep 2018 11:59:53 +0200 Subject: [PATCH 235/586] upgrade libs --- requirements.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0024e5f..e676850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,14 @@ +appdirs==1.4.3 APScheduler==3.5.3 astroid==2.0.4 -attrs==17.4.0 +attrs==18.2.0 +black==18.6b4 chardet==3.0.4 click==6.7 clize==4.0.3 docutils==0.14 -Flask==0.12.2 +Flask==1.0.2 Flask-APScheduler==1.10.1 -Flask-Cors==3.0.3 isort==4.3.4 itsdangerous==0.24 Jinja2==2.10 @@ -17,13 +18,14 @@ Markdown==2.6.11 MarkupSafe==1.0 mccabe==0.6.1 od==1.0 -peewee==2.10.2 +peewee==3.6.4 pylint==2.1.1 PyRSS2Gen==1.1 python-dateutil==2.7.3 pytz==2018.5 -sigtools==2.0.1 +sigtools==2.0.2 six==1.11.0 +toml==0.9.4 tzlocal==1.5.1 Werkzeug==0.14.1 wrapt==1.10.11 From 637b00261aad4dc92bd22d1d79dd44d4e53a1bd6 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 2 Sep 2018 11:59:53 +0200 Subject: [PATCH 236/586] upgrade libs --- requirements.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0024e5f..e676850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,14 @@ +appdirs==1.4.3 APScheduler==3.5.3 astroid==2.0.4 -attrs==17.4.0 +attrs==18.2.0 +black==18.6b4 chardet==3.0.4 click==6.7 clize==4.0.3 docutils==0.14 -Flask==0.12.2 +Flask==1.0.2 Flask-APScheduler==1.10.1 -Flask-Cors==3.0.3 isort==4.3.4 itsdangerous==0.24 Jinja2==2.10 @@ -17,13 +18,14 @@ Markdown==2.6.11 MarkupSafe==1.0 mccabe==0.6.1 od==1.0 -peewee==2.10.2 +peewee==3.6.4 pylint==2.1.1 PyRSS2Gen==1.1 python-dateutil==2.7.3 pytz==2018.5 -sigtools==2.0.1 +sigtools==2.0.2 six==1.11.0 +toml==0.9.4 tzlocal==1.5.1 Werkzeug==0.14.1 wrapt==1.10.11 From 0a2cdbbe8f8947edd2334623019ad9d403cec0d7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Sep 2018 13:38:03 +0200 Subject: [PATCH 237/586] WIP --- README.md | 6 ++ app/__init__.py | 3 - app/conf/config.py | 45 ++++++++++++ app/conf/schema.py | 115 ------------------------------- app/core/__init__.py | 84 ---------------------- app/core/cron.py | 38 +++++----- app/core/database.py | 21 ++---- app/core/processor.py | 6 +- app/core/templater.py | 16 +++++ app/helpers/hashing.py | 2 +- app/interface/__init__.py | 0 app/interface/api.py | 8 +-- app/interface/form.py | 8 +-- app/{models => model}/comment.py | 2 +- app/{models => model}/site.py | 0 app/run.py | 94 +++++++++++++++++++------ config.ini | 21 ++++++ 17 files changed, 197 insertions(+), 272 deletions(-) delete mode 100644 app/conf/schema.py create mode 100644 app/core/templater.py create mode 100644 app/interface/__init__.py rename app/{models => model}/comment.py (95%) rename app/{models => model}/site.py (100%) create mode 100755 config.ini diff --git a/README.md b/README.md index b8dffd5..72017aa 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Stacosys can be hosted on the same server or on a different server than the blog - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) +### Installation + +Python 3.7 + +pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig + ### Ways of improvement Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. diff --git a/app/__init__.py b/app/__init__.py index d7562aa..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +0,0 @@ -from flask import Flask - -app = Flask(__name__) diff --git a/app/conf/config.py b/app/conf/config.py index 3a1213c..9994bc2 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -1,3 +1,48 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import profig + +# constants +FLASK_APP = "flask.app" + +DB_URL = "main.db_url" + +HTTP_HOST = "http.host" +HTTP_PORT = "http.port" + +SECURITY_SALT = "security.salt" +SECURITY_SECRET = "security.secret" + +MAIL_POLLING = "polling.newmail" +COMMENT_POLLING = "polling.newcomment" + +# variable +params = dict() + + +def initialize(config_pathname, flask_app): + cfg = profig.Config(config_pathname) + cfg.sync() + params.update(cfg) + params.update({FLASK_APP: flask_app}) + + +def get(key): + return params[key] + + +def getInt(key): + return int(params[key]) + + +def _str2bool(v): + return v.lower() in ("yes", "true", "t", "1") + + +def getBool(key): + return _str2bool(params[key]) + + +def flaskapp(): + return params[FLASK_APP] diff --git a/app/conf/schema.py b/app/conf/schema.py deleted file mode 100644 index f35e594..0000000 --- a/app/conf/schema.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Created with https://app.quicktype.io -# name: stacosys - -json_schema = """ -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "$ref": "#/definitions/Welcome", - "definitions": { - "Welcome": { - "type": "object", - "additionalProperties": false, - "properties": { - "general": { - "$ref": "#/definitions/General" - }, - "http": { - "$ref": "#/definitions/HTTP" - }, - "security": { - "$ref": "#/definitions/Security" - }, - "rss": { - "$ref": "#/definitions/RSS" - } - }, - "required": [ - "general", - "http", - "rss", - "security" - ], - "title": "Welcome" - }, - "General": { - "type": "object", - "additionalProperties": false, - "properties": { - "debug": { - "type": "boolean" - }, - "lang": { - "type": "string" - }, - "db_url": { - "type": "string" - } - }, - "required": [ - "db_url", - "debug", - "lang" - ], - "title": "General" - }, - "HTTP": { - "type": "object", - "additionalProperties": false, - "properties": { - "root_url": { - "type": "string" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - } - }, - "required": [ - "host", - "port", - "root_url" - ], - "title": "HTTP" - }, - "RSS": { - "type": "object", - "additionalProperties": false, - "properties": { - "proto": { - "type": "string" - }, - "file": { - "type": "string" - } - }, - "required": [ - "file", - "proto" - ], - "title": "RSS" - }, - "Security": { - "type": "object", - "additionalProperties": false, - "properties": { - "salt": { - "type": "string" - }, - "secret": { - "type": "string" - } - }, - "required": [ - "salt", - "secret" - ], - "title": "Security" - } - } -} -""" \ No newline at end of file diff --git a/app/core/__init__.py b/app/core/__init__.py index 48cee40..e69de29 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -1,84 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import os -import sys -import logging -from flask import Flask -from conf import config -from jsonschema import validate -from flask_apscheduler import APScheduler - -app = Flask(__name__) - -# add current path and parent path to syspath -current_path = os.path.dirname(__file__) -parent_path = os.path.abspath(os.path.join(current_path, os.path.pardir)) -paths = [current_path, parent_path] -for path in paths: - if path not in sys.path: - sys.path.insert(0, path) - -# more imports -import database -import processor -from interface import api -from interface import form - -# configure logging -def configure_logging(level): - root_logger = logging.getLogger() - root_logger.setLevel(level) - ch = logging.StreamHandler() - ch.setLevel(level) - # create formatter - formatter = logging.Formatter( - '[%(asctime)s] %(name)s %(levelname)s %(message)s') - # add formatter to ch - ch.setFormatter(formatter) - # add ch to logger - root_logger.addHandler(ch) - -logging_level = (20, 10)[config.general['debug']] -configure_logging(logging_level) - -logger = logging.getLogger(__name__) - -class Config(object): - JOBS = [ - { - 'id': 'fetch_mail', - 'func': 'core.cron:fetch_mail_answers', - 'trigger': 'interval', - 'seconds': 120 - }, - { - 'id': 'submit_new_comment', - 'func': 'core.cron:submit_new_comment', - 'trigger': 'interval', - 'seconds': 60 - }, - ] - -# initialize database -database.setup() - -# start processor -template_path = os.path.abspath(os.path.join(current_path, '../templates')) -processor.start(template_path) - -# cron -app.config.from_object(Config()) -scheduler = APScheduler() -scheduler.init_app(app) -scheduler.start() - -# tune logging level -if not config.general['debug']: - logging.getLogger('werkzeug').level = logging.WARNING - -logger.info("Start Stacosys application") - -app.run(host=config.http['host'], - port=config.http['port'], - debug=config.general['debug'], use_reloader=False) \ No newline at end of file diff --git a/app/core/cron.py b/app/core/cron.py index 77f4077..9788efc 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -3,26 +3,29 @@ import logging import time -from core import app -from core import processor -from models.comment import Comment +from core import mailer +from core import templater +from model.comment import Comment +from model.comment import Site logger = logging.getLogger(__name__) + def fetch_mail_answers(): - logger.info('DEBUT POP MAIL') + logger.info("DEBUT POP MAIL") time.sleep(80) - logger.info('FIN POP MAIL') - #data = request.get_json() - #logger.debug(data) + logger.info("FIN POP MAIL") + # data = request.get_json() + # logger.debug(data) + + # processor.enqueue({'request': 'new_mail', 'data': data}) - #processor.enqueue({'request': 'new_mail', 'data': data}) def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): - # render email body template + comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -33,15 +36,10 @@ def submit_new_comment(): "", ) comment_text = "\n".join(comment_list) - email_body = get_template("new_comment").render(url=url, comment=comment_text) + email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text) - if clientip: - client_ips[comment.id] = clientip - - # send email - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token) - mailer.send_mail(site.admin_email, subject, email_body) - logger.debug("new comment processed ") - -def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") + site = Site.select().where(Site.id == Comment.site).get() + # send email + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) + mailer.send_mail(site.admin_email, subject, email_body) + logger.debug("new comment processed ") diff --git a/app/core/database.py b/app/core/database.py index f6797d8..8404ea2 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -2,26 +2,15 @@ # -*- coding: UTF-8 -*- from conf import config -import functools from playhouse.db_url import connect def get_db(): - return connect(config.general['db_url']) + return connect(config.get(config.DB_URL)) -def provide_db(func): +def setup(): + from model.site import Site + from model.comment import Comment - @functools.wraps(func) - def new_function(*args, **kwargs): - return func(get_db(), *args, **kwargs) - - return new_function - - -@provide_db -def setup(db): - from models.site import Site - from models.comment import Comment - - db.create_tables([Site, Comment], safe=True) + get_db().create_tables([Site, Comment], safe=True) diff --git a/app/core/processor.py b/app/core/processor.py index b94636c..aa63c27 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -10,10 +10,8 @@ import json from datetime import datetime from threading import Thread from queue import Queue -from jinja2 import Environment -from jinja2 import FileSystemLoader -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment from helpers.hashing import md5 from conf import config from core import mailer diff --git a/app/core/templater.py b/app/core/templater.py new file mode 100644 index 0000000..55d59fe --- /dev/null +++ b/app/core/templater.py @@ -0,0 +1,16 @@ + +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +from jinja2 import Environment +from jinja2 import FileSystemLoader +from conf import config + +current_path = os.path.dirname(__file__) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) +env = Environment(loader=FileSystemLoader(template_path)) + + +def get_template(name): + return env.get_template(config.general["lang"] + "/" + name + ".tpl") diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py index 207da43..fd48851 100644 --- a/app/helpers/hashing.py +++ b/app/helpers/hashing.py @@ -6,7 +6,7 @@ from conf import config def salt(value): - string = '%s%s' % (value, config.security['salt']) + string = "%s%s" % (value, config.get(config.SECURITY_SALT)) dk = hashlib.sha256(string.encode()) return dk.hexdigest() diff --git a/app/interface/__init__.py b/app/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/interface/api.py b/app/interface/api.py index 025601c..8aba222 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -3,13 +3,13 @@ import logging from flask import request, jsonify, abort -from core import app -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment +from conf import config from core import processor logger = logging.getLogger(__name__) - +app = config.flaskapp() @app.route("/ping", methods=['GET']) def ping(): diff --git a/app/interface/form.py b/app/interface/form.py index e1e67ec..36620e3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -4,13 +4,13 @@ import logging from datetime import datetime from flask import request, abort, redirect -from core import app -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment +from conf import config from helpers.hashing import md5 logger = logging.getLogger(__name__) - +app = config.flaskapp() @app.route("/newcomment", methods=["POST"]) def new_form_comment(): diff --git a/app/models/comment.py b/app/model/comment.py similarity index 95% rename from app/models/comment.py rename to app/model/comment.py index 10f7301..8cea605 100644 --- a/app/models/comment.py +++ b/app/model/comment.py @@ -6,7 +6,7 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from models.site import Site +from model.site import Site from core.database import get_db diff --git a/app/models/site.py b/app/model/site.py similarity index 100% rename from app/models/site.py rename to app/model/site.py diff --git a/app/run.py b/app/run.py index a46b640..4dfb781 100644 --- a/app/run.py +++ b/app/run.py @@ -1,36 +1,90 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import os import logging -import json from clize import Clize, run -from jsonschema import validate -from conf import config, schema +from flask import Flask +from flask_apscheduler import APScheduler +from conf import config + +# configure logging +def configure_logging(level): + root_logger = logging.getLogger() + root_logger.setLevel(level) + ch = logging.StreamHandler() + ch.setLevel(level) + # create formatter + formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + # add formatter to ch + ch.setFormatter(formatter) + # add ch to logger + root_logger.addHandler(ch) -def load_json(filename): - jsondoc = None - with open(filename, 'rt') as json_file: - jsondoc = json.loads(json_file.read()) - return jsondoc +class JobConfig(object): + + JOBS = [] + + def __init__(self, mail_polling_seconds, new_comment_polling_seconds): + self.JOBS = [ + { + "id": "fetch_mail", + "func": "core.cron:fetch_mail_answers", + "trigger": "interval", + "seconds": mail_polling_seconds, + }, + { + "id": "submit_new_comment", + "func": "core.cron:submit_new_comment", + "trigger": "interval", + "seconds": new_comment_polling_seconds, + }, + ] @Clize def stacosys_server(config_pathname): - # load and validate startup config - conf = load_json(config_pathname) - json_schema = json.loads(schema.json_schema) - validate(conf, json_schema) + app = Flask(__name__) + config.initialize(config_pathname, app) - # set configuration - config.general = conf['general'] - config.http = conf['http'] - config.security = conf['security'] - config.rss = conf['rss'] + # configure logging + logger = logging.getLogger(__name__) + configure_logging(logging.INFO) + logging.getLogger("werkzeug").level = logging.WARNING - # start application - from core import app + # initialize database + from core import database -if __name__ == '__main__': + database.setup() + + # start processor + from core import processor + + # cron email fetcher + app.config.from_object( + JobConfig( + config.getInt(config.MAIL_POLLING), config.getInt(config.COMMENT_POLLING) + ) + ) + scheduler = APScheduler() + scheduler.init_app(app) + scheduler.start() + + logger.info("Start Stacosys application") + + # start Flask + from interface import api + from interface import form + + app.run( + host=config.get(config.HTTP_HOST), + port=config.get(config.HTTP_PORT), + debug=False, + use_reloader=False, + ) + + +if __name__ == "__main__": run(stacosys_server) diff --git a/config.ini b/config.ini new file mode 100755 index 0000000..c91f89d --- /dev/null +++ b/config.ini @@ -0,0 +1,21 @@ +; Default configuration +[main] +lang = fr +db_url = sqlite:///db.sqlite + +[http] +root_url = http://localhost:8100 +host = 0.0.0.0 +port = 8100 + +[security] +salt = BRRJRqXgGpXWrgTidBPcixIThHpDuKc0 +secret = Uqca5Kc8xuU6THz9 + +[rss] +proto = http +file = comments.xml + +[polling] +newmail = 15 +newcomment = 60 From 3c4a25e5ad8a2869235bd0ae66e61454035f87f0 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 15 Sep 2018 13:38:03 +0200 Subject: [PATCH 238/586] WIP --- README.md | 6 ++ app/__init__.py | 3 - app/conf/config.py | 45 ++++++++++++ app/conf/schema.py | 115 ------------------------------- app/core/__init__.py | 84 ---------------------- app/core/cron.py | 38 +++++----- app/core/database.py | 21 ++---- app/core/processor.py | 6 +- app/core/templater.py | 16 +++++ app/helpers/hashing.py | 2 +- app/interface/__init__.py | 0 app/interface/api.py | 8 +-- app/interface/form.py | 8 +-- app/{models => model}/comment.py | 2 +- app/{models => model}/site.py | 0 app/run.py | 94 +++++++++++++++++++------ config.ini | 21 ++++++ 17 files changed, 197 insertions(+), 272 deletions(-) delete mode 100644 app/conf/schema.py create mode 100644 app/core/templater.py create mode 100644 app/interface/__init__.py rename app/{models => model}/comment.py (95%) rename app/{models => model}/site.py (100%) create mode 100755 config.ini diff --git a/README.md b/README.md index b8dffd5..72017aa 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Stacosys can be hosted on the same server or on a different server than the blog - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) +### Installation + +Python 3.7 + +pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig + ### Ways of improvement Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. diff --git a/app/__init__.py b/app/__init__.py index d7562aa..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +0,0 @@ -from flask import Flask - -app = Flask(__name__) diff --git a/app/conf/config.py b/app/conf/config.py index 3a1213c..9994bc2 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -1,3 +1,48 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import profig + +# constants +FLASK_APP = "flask.app" + +DB_URL = "main.db_url" + +HTTP_HOST = "http.host" +HTTP_PORT = "http.port" + +SECURITY_SALT = "security.salt" +SECURITY_SECRET = "security.secret" + +MAIL_POLLING = "polling.newmail" +COMMENT_POLLING = "polling.newcomment" + +# variable +params = dict() + + +def initialize(config_pathname, flask_app): + cfg = profig.Config(config_pathname) + cfg.sync() + params.update(cfg) + params.update({FLASK_APP: flask_app}) + + +def get(key): + return params[key] + + +def getInt(key): + return int(params[key]) + + +def _str2bool(v): + return v.lower() in ("yes", "true", "t", "1") + + +def getBool(key): + return _str2bool(params[key]) + + +def flaskapp(): + return params[FLASK_APP] diff --git a/app/conf/schema.py b/app/conf/schema.py deleted file mode 100644 index f35e594..0000000 --- a/app/conf/schema.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Created with https://app.quicktype.io -# name: stacosys - -json_schema = """ -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "$ref": "#/definitions/Welcome", - "definitions": { - "Welcome": { - "type": "object", - "additionalProperties": false, - "properties": { - "general": { - "$ref": "#/definitions/General" - }, - "http": { - "$ref": "#/definitions/HTTP" - }, - "security": { - "$ref": "#/definitions/Security" - }, - "rss": { - "$ref": "#/definitions/RSS" - } - }, - "required": [ - "general", - "http", - "rss", - "security" - ], - "title": "Welcome" - }, - "General": { - "type": "object", - "additionalProperties": false, - "properties": { - "debug": { - "type": "boolean" - }, - "lang": { - "type": "string" - }, - "db_url": { - "type": "string" - } - }, - "required": [ - "db_url", - "debug", - "lang" - ], - "title": "General" - }, - "HTTP": { - "type": "object", - "additionalProperties": false, - "properties": { - "root_url": { - "type": "string" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - } - }, - "required": [ - "host", - "port", - "root_url" - ], - "title": "HTTP" - }, - "RSS": { - "type": "object", - "additionalProperties": false, - "properties": { - "proto": { - "type": "string" - }, - "file": { - "type": "string" - } - }, - "required": [ - "file", - "proto" - ], - "title": "RSS" - }, - "Security": { - "type": "object", - "additionalProperties": false, - "properties": { - "salt": { - "type": "string" - }, - "secret": { - "type": "string" - } - }, - "required": [ - "salt", - "secret" - ], - "title": "Security" - } - } -} -""" \ No newline at end of file diff --git a/app/core/__init__.py b/app/core/__init__.py index 48cee40..e69de29 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -1,84 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import os -import sys -import logging -from flask import Flask -from conf import config -from jsonschema import validate -from flask_apscheduler import APScheduler - -app = Flask(__name__) - -# add current path and parent path to syspath -current_path = os.path.dirname(__file__) -parent_path = os.path.abspath(os.path.join(current_path, os.path.pardir)) -paths = [current_path, parent_path] -for path in paths: - if path not in sys.path: - sys.path.insert(0, path) - -# more imports -import database -import processor -from interface import api -from interface import form - -# configure logging -def configure_logging(level): - root_logger = logging.getLogger() - root_logger.setLevel(level) - ch = logging.StreamHandler() - ch.setLevel(level) - # create formatter - formatter = logging.Formatter( - '[%(asctime)s] %(name)s %(levelname)s %(message)s') - # add formatter to ch - ch.setFormatter(formatter) - # add ch to logger - root_logger.addHandler(ch) - -logging_level = (20, 10)[config.general['debug']] -configure_logging(logging_level) - -logger = logging.getLogger(__name__) - -class Config(object): - JOBS = [ - { - 'id': 'fetch_mail', - 'func': 'core.cron:fetch_mail_answers', - 'trigger': 'interval', - 'seconds': 120 - }, - { - 'id': 'submit_new_comment', - 'func': 'core.cron:submit_new_comment', - 'trigger': 'interval', - 'seconds': 60 - }, - ] - -# initialize database -database.setup() - -# start processor -template_path = os.path.abspath(os.path.join(current_path, '../templates')) -processor.start(template_path) - -# cron -app.config.from_object(Config()) -scheduler = APScheduler() -scheduler.init_app(app) -scheduler.start() - -# tune logging level -if not config.general['debug']: - logging.getLogger('werkzeug').level = logging.WARNING - -logger.info("Start Stacosys application") - -app.run(host=config.http['host'], - port=config.http['port'], - debug=config.general['debug'], use_reloader=False) \ No newline at end of file diff --git a/app/core/cron.py b/app/core/cron.py index 77f4077..9788efc 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -3,26 +3,29 @@ import logging import time -from core import app -from core import processor -from models.comment import Comment +from core import mailer +from core import templater +from model.comment import Comment +from model.comment import Site logger = logging.getLogger(__name__) + def fetch_mail_answers(): - logger.info('DEBUT POP MAIL') + logger.info("DEBUT POP MAIL") time.sleep(80) - logger.info('FIN POP MAIL') - #data = request.get_json() - #logger.debug(data) + logger.info("FIN POP MAIL") + # data = request.get_json() + # logger.debug(data) + + # processor.enqueue({'request': 'new_mail', 'data': data}) - #processor.enqueue({'request': 'new_mail', 'data': data}) def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): - # render email body template + comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -33,15 +36,10 @@ def submit_new_comment(): "", ) comment_text = "\n".join(comment_list) - email_body = get_template("new_comment").render(url=url, comment=comment_text) + email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text) - if clientip: - client_ips[comment.id] = clientip - - # send email - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token) - mailer.send_mail(site.admin_email, subject, email_body) - logger.debug("new comment processed ") - -def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") + site = Site.select().where(Site.id == Comment.site).get() + # send email + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) + mailer.send_mail(site.admin_email, subject, email_body) + logger.debug("new comment processed ") diff --git a/app/core/database.py b/app/core/database.py index f6797d8..8404ea2 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -2,26 +2,15 @@ # -*- coding: UTF-8 -*- from conf import config -import functools from playhouse.db_url import connect def get_db(): - return connect(config.general['db_url']) + return connect(config.get(config.DB_URL)) -def provide_db(func): +def setup(): + from model.site import Site + from model.comment import Comment - @functools.wraps(func) - def new_function(*args, **kwargs): - return func(get_db(), *args, **kwargs) - - return new_function - - -@provide_db -def setup(db): - from models.site import Site - from models.comment import Comment - - db.create_tables([Site, Comment], safe=True) + get_db().create_tables([Site, Comment], safe=True) diff --git a/app/core/processor.py b/app/core/processor.py index b94636c..aa63c27 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -10,10 +10,8 @@ import json from datetime import datetime from threading import Thread from queue import Queue -from jinja2 import Environment -from jinja2 import FileSystemLoader -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment from helpers.hashing import md5 from conf import config from core import mailer diff --git a/app/core/templater.py b/app/core/templater.py new file mode 100644 index 0000000..55d59fe --- /dev/null +++ b/app/core/templater.py @@ -0,0 +1,16 @@ + +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +from jinja2 import Environment +from jinja2 import FileSystemLoader +from conf import config + +current_path = os.path.dirname(__file__) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) +env = Environment(loader=FileSystemLoader(template_path)) + + +def get_template(name): + return env.get_template(config.general["lang"] + "/" + name + ".tpl") diff --git a/app/helpers/hashing.py b/app/helpers/hashing.py index 207da43..fd48851 100644 --- a/app/helpers/hashing.py +++ b/app/helpers/hashing.py @@ -6,7 +6,7 @@ from conf import config def salt(value): - string = '%s%s' % (value, config.security['salt']) + string = "%s%s" % (value, config.get(config.SECURITY_SALT)) dk = hashlib.sha256(string.encode()) return dk.hexdigest() diff --git a/app/interface/__init__.py b/app/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/interface/api.py b/app/interface/api.py index 025601c..8aba222 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -3,13 +3,13 @@ import logging from flask import request, jsonify, abort -from core import app -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment +from conf import config from core import processor logger = logging.getLogger(__name__) - +app = config.flaskapp() @app.route("/ping", methods=['GET']) def ping(): diff --git a/app/interface/form.py b/app/interface/form.py index e1e67ec..36620e3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -4,13 +4,13 @@ import logging from datetime import datetime from flask import request, abort, redirect -from core import app -from models.site import Site -from models.comment import Comment +from model.site import Site +from model.comment import Comment +from conf import config from helpers.hashing import md5 logger = logging.getLogger(__name__) - +app = config.flaskapp() @app.route("/newcomment", methods=["POST"]) def new_form_comment(): diff --git a/app/models/comment.py b/app/model/comment.py similarity index 95% rename from app/models/comment.py rename to app/model/comment.py index 10f7301..8cea605 100644 --- a/app/models/comment.py +++ b/app/model/comment.py @@ -6,7 +6,7 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from models.site import Site +from model.site import Site from core.database import get_db diff --git a/app/models/site.py b/app/model/site.py similarity index 100% rename from app/models/site.py rename to app/model/site.py diff --git a/app/run.py b/app/run.py index a46b640..4dfb781 100644 --- a/app/run.py +++ b/app/run.py @@ -1,36 +1,90 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import os import logging -import json from clize import Clize, run -from jsonschema import validate -from conf import config, schema +from flask import Flask +from flask_apscheduler import APScheduler +from conf import config + +# configure logging +def configure_logging(level): + root_logger = logging.getLogger() + root_logger.setLevel(level) + ch = logging.StreamHandler() + ch.setLevel(level) + # create formatter + formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + # add formatter to ch + ch.setFormatter(formatter) + # add ch to logger + root_logger.addHandler(ch) -def load_json(filename): - jsondoc = None - with open(filename, 'rt') as json_file: - jsondoc = json.loads(json_file.read()) - return jsondoc +class JobConfig(object): + + JOBS = [] + + def __init__(self, mail_polling_seconds, new_comment_polling_seconds): + self.JOBS = [ + { + "id": "fetch_mail", + "func": "core.cron:fetch_mail_answers", + "trigger": "interval", + "seconds": mail_polling_seconds, + }, + { + "id": "submit_new_comment", + "func": "core.cron:submit_new_comment", + "trigger": "interval", + "seconds": new_comment_polling_seconds, + }, + ] @Clize def stacosys_server(config_pathname): - # load and validate startup config - conf = load_json(config_pathname) - json_schema = json.loads(schema.json_schema) - validate(conf, json_schema) + app = Flask(__name__) + config.initialize(config_pathname, app) - # set configuration - config.general = conf['general'] - config.http = conf['http'] - config.security = conf['security'] - config.rss = conf['rss'] + # configure logging + logger = logging.getLogger(__name__) + configure_logging(logging.INFO) + logging.getLogger("werkzeug").level = logging.WARNING - # start application - from core import app + # initialize database + from core import database -if __name__ == '__main__': + database.setup() + + # start processor + from core import processor + + # cron email fetcher + app.config.from_object( + JobConfig( + config.getInt(config.MAIL_POLLING), config.getInt(config.COMMENT_POLLING) + ) + ) + scheduler = APScheduler() + scheduler.init_app(app) + scheduler.start() + + logger.info("Start Stacosys application") + + # start Flask + from interface import api + from interface import form + + app.run( + host=config.get(config.HTTP_HOST), + port=config.get(config.HTTP_PORT), + debug=False, + use_reloader=False, + ) + + +if __name__ == "__main__": run(stacosys_server) diff --git a/config.ini b/config.ini new file mode 100755 index 0000000..c91f89d --- /dev/null +++ b/config.ini @@ -0,0 +1,21 @@ +; Default configuration +[main] +lang = fr +db_url = sqlite:///db.sqlite + +[http] +root_url = http://localhost:8100 +host = 0.0.0.0 +port = 8100 + +[security] +salt = BRRJRqXgGpXWrgTidBPcixIThHpDuKc0 +secret = Uqca5Kc8xuU6THz9 + +[rss] +proto = http +file = comments.xml + +[polling] +newmail = 15 +newcomment = 60 From 9146588902b50d07048fa381de9bb81aa381aae6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Sep 2018 15:02:06 +0200 Subject: [PATCH 239/586] WIP --- app/__init__.py | 0 app/conf/config.py | 10 +- app/core/cron.py | 105 +++++++++++++++++-- app/core/mailer.py | 12 +++ app/core/processor.py | 160 +---------------------------- app/core/rss.py | 52 ++++++++++ app/core/templater.py | 3 +- app/{helpers => helper}/hashing.py | 0 app/interface/form.py | 2 +- app/run.py | 11 +- config.ini | 7 +- 11 files changed, 185 insertions(+), 177 deletions(-) delete mode 100644 app/__init__.py create mode 100644 app/core/rss.py rename app/{helpers => helper}/hashing.py (100%) diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/conf/config.py b/app/conf/config.py index 9994bc2..a9cd9b3 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -7,6 +7,7 @@ import profig FLASK_APP = "flask.app" DB_URL = "main.db_url" +LANG = "main.lang" HTTP_HOST = "http.host" HTTP_PORT = "http.port" @@ -14,8 +15,13 @@ HTTP_PORT = "http.port" SECURITY_SALT = "security.salt" SECURITY_SECRET = "security.secret" -MAIL_POLLING = "polling.newmail" -COMMENT_POLLING = "polling.newcomment" +RSS_PROTO = "rss.proto" +RSS_FILE = "rss.file" + +MAIL_POLLING = "mail.fetch_polling" +COMMENT_POLLING = "main.newcomment_polling" +MAILER_URL = "mail.mailer_url" + # variable params = dict() diff --git a/app/core/cron.py b/app/core/cron.py index 9788efc..932bf83 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -2,30 +2,48 @@ # -*- coding: utf-8 -*- import logging +from datetime import datetime import time +import re from core import mailer -from core import templater +from core.templater import get_template +from core import rss from model.comment import Comment from model.comment import Site logger = logging.getLogger(__name__) +client_ips = {} + +def cron(func): + def wrapper(): + logger.debug("execute fun " + func) + func() + + return wrapper + + +@cron def fetch_mail_answers(): - logger.info("DEBUT POP MAIL") - time.sleep(80) - logger.info("FIN POP MAIL") + msg = {} + + if msg["request"] == "new_mail": + reply_comment_email(msg["data"]) + mailer.delete(msg["data"]) + # data = request.get_json() # logger.debug(data) # processor.enqueue({'request': 'new_mail', 'data': data}) +@cron def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): - + comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -36,10 +54,83 @@ def submit_new_comment(): "", ) comment_text = "\n".join(comment_list) - email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text) + email_body = get_template("new_comment").render( + url=comment.url, comment=comment_text + ) site = Site.select().where(Site.id == Comment.site).get() # send email subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - mailer.send_mail(site.admin_email, subject, email_body) + mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") + + +def reply_comment_email(data): + + from_email = data["from"] + subject = data["subject"] + message = "" + for part in data["parts"]: + if part["content-type"] == "text/plain": + message = part["content"] + break + + m = re.search(r"\[(\d+)\:(\w+)\]", subject) + if not m: + logger.warn("ignore corrupted email. No token %s" % subject) + return + comment_id = int(m.group(1)) + token = m.group(2) + + # retrieve site and comment rows + try: + comment = Comment.select().where(Comment.id == comment_id).get() + except: + logger.warn("unknown comment %d" % comment_id) + return + + if comment.published: + logger.warn("ignore already published email. token %d" % comment_id) + return + + if comment.site.token != token: + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + return + + if not message: + logger.warn("ignore empty email") + return + + # safe logic: no answer or unknown answer is a go for publishing + if message[:2].upper() in ("NO", "SP"): + + # put a log to help fail2ban + if message[:2].upper() == "SP": # SPAM + if comment_id in client_ips: + logger.info( + "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + ) + else: + logger.info("cannot identify SPAM source: %d" % comment_id) + + # forget client IP + if comment_id in client_ips: + del client_ips[comment_id] + + logger.info("discard comment: %d" % comment_id) + comment.delete_instance() + email_body = get_template("drop_comment").render(original=message) + mailer.send(from_email, "Re: " + subject, email_body) + else: + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + logger.info("commit comment: %d" % comment_id) + + # rebuild RSS + rss.generate_site(token) + + # send approval confirmation email to admin + email_body = get_template("approve_comment").render(original=message) + mailer.send(from_email, "Re: " + subject, email_body) + diff --git a/app/core/mailer.py b/app/core/mailer.py index 3a1213c..b58e6e1 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -1,3 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +def fetch(): + pass + + +def send(email, subject, body): + pass + + +def delete(content): + # TODO delete mail + pass diff --git a/app/core/processor.py b/app/core/processor.py index aa63c27..148924d 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -12,7 +12,7 @@ from threading import Thread from queue import Queue from model.site import Site from model.comment import Comment -from helpers.hashing import md5 +from helper.hashing import md5 from conf import config from core import mailer @@ -26,95 +26,7 @@ env = None client_ips = {} -class Processor(Thread): - def stop(self): - logger.info("stop requested") - self.is_running = False - def run(self): - - logger.info("processor thread started") - self.is_running = True - while self.is_running: - try: - msg = queue.get() - if msg["request"] == "new_mail": - reply_comment_email(msg["data"]) - send_delete_command(msg["data"]) - else: - logger.info("throw unknown request " + str(msg)) - except: - logger.exception("processing failure") - - -def reply_comment_email(data): - - from_email = data["from"] - subject = data["subject"] - message = "" - for part in data["parts"]: - if part["content-type"] == "text/plain": - message = part["content"] - break - - m = re.search("\[(\d+)\:(\w+)\]", subject) - if not m: - logger.warn("ignore corrupted email. No token %s" % subject) - return - comment_id = int(m.group(1)) - token = m.group(2) - - # retrieve site and comment rows - try: - comment = Comment.select().where(Comment.id == comment_id).get() - except: - logger.warn("unknown comment %d" % comment_id) - return - - if comment.published: - logger.warn("ignore already published email. token %d" % comment_id) - return - - if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) - return - - if not message: - logger.warn("ignore empty email") - return - - # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO", "SP"): - - # put a log to help fail2ban - if message[:2].upper() == "SP": # SPAM - if comment_id in client_ips: - logger.info( - "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) - ) - else: - logger.info("cannot identify SPAM source: %d" % comment_id) - - # forget client IP - if comment_id in client_ips: - del client_ips[comment_id] - - logger.info("discard comment: %d" % comment_id) - comment.delete_instance() - email_body = get_template("drop_comment").render(original=message) - mail(from_email, "Re: " + subject, email_body) - else: - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() - logger.info("commit comment: %d" % comment_id) - - # rebuild RSS - rss(token) - - # send approval confirmation email to admin - email_body = get_template("approve_comment").render(original=message) - mail(from_email, "Re: " + subject, email_body) def get_email_metadata(message): @@ -126,73 +38,3 @@ def get_email_metadata(message): return email -def rss(token, onstart=False): - - if onstart and os.path.isfile(config.rss["file"]): - return - - site = Site.select().where(Site.token == token).get() - rss_title = get_template("rss_title_message").render(site=site.name) - md = markdown.Markdown() - - items = [] - for row in ( - Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) - .order_by(-Comment.published) - .limit(10) - ): - item_link = "%s://%s%s" % (config.rss["proto"], site.url, row.url) - items.append( - PyRSS2Gen.RSSItem( - title="%s - %s://%s%s" - % (config.rss["proto"], row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), - pubDate=row.published, - ) - ) - - rss = PyRSS2Gen.RSS2( - title=rss_title, - link="%s://%s" % (config.rss["proto"], site.url), - description="Commentaires du site '%s'" % site.name, - lastBuildDate=datetime.now(), - items=items, - ) - rss.write_xml(open(config.rss["file"], "w"), encoding="utf-8") - - -def send_delete_command(content): - # TODO delete mail - pass - - -def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") - - -def enqueue(something): - queue.put(something) - - -def get_processor(): - return proc - - -def start(template_dir): - global proc, env - - # initialize Jinja 2 templating - logger.info("load templates from directory %s" % template_dir) - env = Environment(loader=FileSystemLoader(template_dir)) - - # generate RSS for all sites - for site in Site.select(): - rss(site.token, True) - - # start processor thread - proc = Processor() - proc.start() diff --git a/app/core/rss.py b/app/core/rss.py new file mode 100644 index 0000000..97f4637 --- /dev/null +++ b/app/core/rss.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from datetime import datetime +import markdown +import PyRSS2Gen +from model.site import Site +from model.comment import Comment +from core.templater import get_template +from conf import config + + +def generate_all(): + for site in Site.select(): + generate_site(site.token) + + +def generate_site(token): + + site = Site.select().where(Site.token == token).get() + rss_title = get_template("rss_title_message").render(site=site.name) + md = markdown.Markdown() + + items = [] + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (config.get(config.RSS_PROTO), site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) + ) + + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="%s://%s" % (config.get(config.RSS_PROTO), site.url), + description="Commentaires du site '%s'" % site.name, + lastBuildDate=datetime.now(), + items=items, + ) + rss.write_xml(open(config.get(config.RSS_FILE), "w"), encoding="utf-8") + diff --git a/app/core/templater.py b/app/core/templater.py index 55d59fe..56b5278 100644 --- a/app/core/templater.py +++ b/app/core/templater.py @@ -1,4 +1,3 @@ - #!/usr/bin/env python # -*- coding: utf-8 -*- @@ -13,4 +12,4 @@ env = Environment(loader=FileSystemLoader(template_path)) def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") + return env.get_template(config.get(config.LANG) + "/" + name + ".tpl") diff --git a/app/helpers/hashing.py b/app/helper/hashing.py similarity index 100% rename from app/helpers/hashing.py rename to app/helper/hashing.py diff --git a/app/interface/form.py b/app/interface/form.py index 36620e3..00cc149 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -7,7 +7,7 @@ from flask import request, abort, redirect from model.site import Site from model.comment import Comment from conf import config -from helpers.hashing import md5 +from helper.hashing import md5 logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/app/run.py b/app/run.py index 4dfb781..c57aa3a 100644 --- a/app/run.py +++ b/app/run.py @@ -59,9 +59,6 @@ def stacosys_server(config_pathname): database.setup() - # start processor - from core import processor - # cron email fetcher app.config.from_object( JobConfig( @@ -74,10 +71,18 @@ def stacosys_server(config_pathname): logger.info("Start Stacosys application") + # generate RSS for all sites + from core import rss + + rss.generate_all() + # start Flask from interface import api from interface import form + logger.debug("Load interface %s" % api) + logger.debug("Load interface %s" % form) + app.run( host=config.get(config.HTTP_HOST), port=config.get(config.HTTP_PORT), diff --git a/config.ini b/config.ini index c91f89d..bd26c6a 100755 --- a/config.ini +++ b/config.ini @@ -2,6 +2,7 @@ [main] lang = fr db_url = sqlite:///db.sqlite +newcomment_polling = 60 [http] root_url = http://localhost:8100 @@ -16,6 +17,6 @@ secret = Uqca5Kc8xuU6THz9 proto = http file = comments.xml -[polling] -newmail = 15 -newcomment = 60 +[mail] +fetch_polling = 15 +mailer_url = http://localhost:8000 From 6867e71d7cc859e99b76d804c4d151beddf9664d Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 15 Sep 2018 15:02:06 +0200 Subject: [PATCH 240/586] WIP --- app/__init__.py | 0 app/conf/config.py | 10 +- app/core/cron.py | 105 +++++++++++++++++-- app/core/mailer.py | 12 +++ app/core/processor.py | 160 +---------------------------- app/core/rss.py | 52 ++++++++++ app/core/templater.py | 3 +- app/{helpers => helper}/hashing.py | 0 app/interface/form.py | 2 +- app/run.py | 11 +- config.ini | 7 +- 11 files changed, 185 insertions(+), 177 deletions(-) delete mode 100644 app/__init__.py create mode 100644 app/core/rss.py rename app/{helpers => helper}/hashing.py (100%) diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/conf/config.py b/app/conf/config.py index 9994bc2..a9cd9b3 100644 --- a/app/conf/config.py +++ b/app/conf/config.py @@ -7,6 +7,7 @@ import profig FLASK_APP = "flask.app" DB_URL = "main.db_url" +LANG = "main.lang" HTTP_HOST = "http.host" HTTP_PORT = "http.port" @@ -14,8 +15,13 @@ HTTP_PORT = "http.port" SECURITY_SALT = "security.salt" SECURITY_SECRET = "security.secret" -MAIL_POLLING = "polling.newmail" -COMMENT_POLLING = "polling.newcomment" +RSS_PROTO = "rss.proto" +RSS_FILE = "rss.file" + +MAIL_POLLING = "mail.fetch_polling" +COMMENT_POLLING = "main.newcomment_polling" +MAILER_URL = "mail.mailer_url" + # variable params = dict() diff --git a/app/core/cron.py b/app/core/cron.py index 9788efc..932bf83 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -2,30 +2,48 @@ # -*- coding: utf-8 -*- import logging +from datetime import datetime import time +import re from core import mailer -from core import templater +from core.templater import get_template +from core import rss from model.comment import Comment from model.comment import Site logger = logging.getLogger(__name__) +client_ips = {} + +def cron(func): + def wrapper(): + logger.debug("execute fun " + func) + func() + + return wrapper + + +@cron def fetch_mail_answers(): - logger.info("DEBUT POP MAIL") - time.sleep(80) - logger.info("FIN POP MAIL") + msg = {} + + if msg["request"] == "new_mail": + reply_comment_email(msg["data"]) + mailer.delete(msg["data"]) + # data = request.get_json() # logger.debug(data) # processor.enqueue({'request': 'new_mail', 'data': data}) +@cron def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): - + comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -36,10 +54,83 @@ def submit_new_comment(): "", ) comment_text = "\n".join(comment_list) - email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text) + email_body = get_template("new_comment").render( + url=comment.url, comment=comment_text + ) site = Site.select().where(Site.id == Comment.site).get() # send email subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - mailer.send_mail(site.admin_email, subject, email_body) + mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") + + +def reply_comment_email(data): + + from_email = data["from"] + subject = data["subject"] + message = "" + for part in data["parts"]: + if part["content-type"] == "text/plain": + message = part["content"] + break + + m = re.search(r"\[(\d+)\:(\w+)\]", subject) + if not m: + logger.warn("ignore corrupted email. No token %s" % subject) + return + comment_id = int(m.group(1)) + token = m.group(2) + + # retrieve site and comment rows + try: + comment = Comment.select().where(Comment.id == comment_id).get() + except: + logger.warn("unknown comment %d" % comment_id) + return + + if comment.published: + logger.warn("ignore already published email. token %d" % comment_id) + return + + if comment.site.token != token: + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + return + + if not message: + logger.warn("ignore empty email") + return + + # safe logic: no answer or unknown answer is a go for publishing + if message[:2].upper() in ("NO", "SP"): + + # put a log to help fail2ban + if message[:2].upper() == "SP": # SPAM + if comment_id in client_ips: + logger.info( + "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + ) + else: + logger.info("cannot identify SPAM source: %d" % comment_id) + + # forget client IP + if comment_id in client_ips: + del client_ips[comment_id] + + logger.info("discard comment: %d" % comment_id) + comment.delete_instance() + email_body = get_template("drop_comment").render(original=message) + mailer.send(from_email, "Re: " + subject, email_body) + else: + # update Comment row + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + logger.info("commit comment: %d" % comment_id) + + # rebuild RSS + rss.generate_site(token) + + # send approval confirmation email to admin + email_body = get_template("approve_comment").render(original=message) + mailer.send(from_email, "Re: " + subject, email_body) + diff --git a/app/core/mailer.py b/app/core/mailer.py index 3a1213c..b58e6e1 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -1,3 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +def fetch(): + pass + + +def send(email, subject, body): + pass + + +def delete(content): + # TODO delete mail + pass diff --git a/app/core/processor.py b/app/core/processor.py index aa63c27..148924d 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -12,7 +12,7 @@ from threading import Thread from queue import Queue from model.site import Site from model.comment import Comment -from helpers.hashing import md5 +from helper.hashing import md5 from conf import config from core import mailer @@ -26,95 +26,7 @@ env = None client_ips = {} -class Processor(Thread): - def stop(self): - logger.info("stop requested") - self.is_running = False - def run(self): - - logger.info("processor thread started") - self.is_running = True - while self.is_running: - try: - msg = queue.get() - if msg["request"] == "new_mail": - reply_comment_email(msg["data"]) - send_delete_command(msg["data"]) - else: - logger.info("throw unknown request " + str(msg)) - except: - logger.exception("processing failure") - - -def reply_comment_email(data): - - from_email = data["from"] - subject = data["subject"] - message = "" - for part in data["parts"]: - if part["content-type"] == "text/plain": - message = part["content"] - break - - m = re.search("\[(\d+)\:(\w+)\]", subject) - if not m: - logger.warn("ignore corrupted email. No token %s" % subject) - return - comment_id = int(m.group(1)) - token = m.group(2) - - # retrieve site and comment rows - try: - comment = Comment.select().where(Comment.id == comment_id).get() - except: - logger.warn("unknown comment %d" % comment_id) - return - - if comment.published: - logger.warn("ignore already published email. token %d" % comment_id) - return - - if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) - return - - if not message: - logger.warn("ignore empty email") - return - - # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO", "SP"): - - # put a log to help fail2ban - if message[:2].upper() == "SP": # SPAM - if comment_id in client_ips: - logger.info( - "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) - ) - else: - logger.info("cannot identify SPAM source: %d" % comment_id) - - # forget client IP - if comment_id in client_ips: - del client_ips[comment_id] - - logger.info("discard comment: %d" % comment_id) - comment.delete_instance() - email_body = get_template("drop_comment").render(original=message) - mail(from_email, "Re: " + subject, email_body) - else: - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() - logger.info("commit comment: %d" % comment_id) - - # rebuild RSS - rss(token) - - # send approval confirmation email to admin - email_body = get_template("approve_comment").render(original=message) - mail(from_email, "Re: " + subject, email_body) def get_email_metadata(message): @@ -126,73 +38,3 @@ def get_email_metadata(message): return email -def rss(token, onstart=False): - - if onstart and os.path.isfile(config.rss["file"]): - return - - site = Site.select().where(Site.token == token).get() - rss_title = get_template("rss_title_message").render(site=site.name) - md = markdown.Markdown() - - items = [] - for row in ( - Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) - .order_by(-Comment.published) - .limit(10) - ): - item_link = "%s://%s%s" % (config.rss["proto"], site.url, row.url) - items.append( - PyRSS2Gen.RSSItem( - title="%s - %s://%s%s" - % (config.rss["proto"], row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), - pubDate=row.published, - ) - ) - - rss = PyRSS2Gen.RSS2( - title=rss_title, - link="%s://%s" % (config.rss["proto"], site.url), - description="Commentaires du site '%s'" % site.name, - lastBuildDate=datetime.now(), - items=items, - ) - rss.write_xml(open(config.rss["file"], "w"), encoding="utf-8") - - -def send_delete_command(content): - # TODO delete mail - pass - - -def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") - - -def enqueue(something): - queue.put(something) - - -def get_processor(): - return proc - - -def start(template_dir): - global proc, env - - # initialize Jinja 2 templating - logger.info("load templates from directory %s" % template_dir) - env = Environment(loader=FileSystemLoader(template_dir)) - - # generate RSS for all sites - for site in Site.select(): - rss(site.token, True) - - # start processor thread - proc = Processor() - proc.start() diff --git a/app/core/rss.py b/app/core/rss.py new file mode 100644 index 0000000..97f4637 --- /dev/null +++ b/app/core/rss.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from datetime import datetime +import markdown +import PyRSS2Gen +from model.site import Site +from model.comment import Comment +from core.templater import get_template +from conf import config + + +def generate_all(): + for site in Site.select(): + generate_site(site.token) + + +def generate_site(token): + + site = Site.select().where(Site.token == token).get() + rss_title = get_template("rss_title_message").render(site=site.name) + md = markdown.Markdown() + + items = [] + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (config.get(config.RSS_PROTO), site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) + ) + + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="%s://%s" % (config.get(config.RSS_PROTO), site.url), + description="Commentaires du site '%s'" % site.name, + lastBuildDate=datetime.now(), + items=items, + ) + rss.write_xml(open(config.get(config.RSS_FILE), "w"), encoding="utf-8") + diff --git a/app/core/templater.py b/app/core/templater.py index 55d59fe..56b5278 100644 --- a/app/core/templater.py +++ b/app/core/templater.py @@ -1,4 +1,3 @@ - #!/usr/bin/env python # -*- coding: utf-8 -*- @@ -13,4 +12,4 @@ env = Environment(loader=FileSystemLoader(template_path)) def get_template(name): - return env.get_template(config.general["lang"] + "/" + name + ".tpl") + return env.get_template(config.get(config.LANG) + "/" + name + ".tpl") diff --git a/app/helpers/hashing.py b/app/helper/hashing.py similarity index 100% rename from app/helpers/hashing.py rename to app/helper/hashing.py diff --git a/app/interface/form.py b/app/interface/form.py index 36620e3..00cc149 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -7,7 +7,7 @@ from flask import request, abort, redirect from model.site import Site from model.comment import Comment from conf import config -from helpers.hashing import md5 +from helper.hashing import md5 logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/app/run.py b/app/run.py index 4dfb781..c57aa3a 100644 --- a/app/run.py +++ b/app/run.py @@ -59,9 +59,6 @@ def stacosys_server(config_pathname): database.setup() - # start processor - from core import processor - # cron email fetcher app.config.from_object( JobConfig( @@ -74,10 +71,18 @@ def stacosys_server(config_pathname): logger.info("Start Stacosys application") + # generate RSS for all sites + from core import rss + + rss.generate_all() + # start Flask from interface import api from interface import form + logger.debug("Load interface %s" % api) + logger.debug("Load interface %s" % form) + app.run( host=config.get(config.HTTP_HOST), port=config.get(config.HTTP_PORT), diff --git a/config.ini b/config.ini index c91f89d..bd26c6a 100755 --- a/config.ini +++ b/config.ini @@ -2,6 +2,7 @@ [main] lang = fr db_url = sqlite:///db.sqlite +newcomment_polling = 60 [http] root_url = http://localhost:8100 @@ -16,6 +17,6 @@ secret = Uqca5Kc8xuU6THz9 proto = http file = comments.xml -[polling] -newmail = 15 -newcomment = 60 +[mail] +fetch_polling = 15 +mailer_url = http://localhost:8000 From 2554c716da8e0b9c897fb233fd58b09e8dc76321 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Sep 2018 15:50:25 +0200 Subject: [PATCH 241/586] WIP --- app/core/cron.py | 30 +++++++++++------------------- app/core/mailer.py | 41 +++++++++++++++++++++++++++++++++++------ app/core/processor.py | 40 ---------------------------------------- app/interface/api.py | 1 - 4 files changed, 46 insertions(+), 66 deletions(-) delete mode 100644 app/core/processor.py diff --git a/app/core/cron.py b/app/core/cron.py index 932bf83..fcd8137 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -13,12 +13,10 @@ from model.comment import Site logger = logging.getLogger(__name__) -client_ips = {} - def cron(func): def wrapper(): - logger.debug("execute fun " + func) + logger.debug("execute CRON " + func.__name__) func() return wrapper @@ -27,16 +25,13 @@ def cron(func): @cron def fetch_mail_answers(): - msg = {} - - if msg["request"] == "new_mail": - reply_comment_email(msg["data"]) - mailer.delete(msg["data"]) - - # data = request.get_json() - # logger.debug(data) - - # processor.enqueue({'request': 'new_mail', 'data': data}) + for msg in mailer.fetch(): + m = re.search(r"\[(\d+)\:(\w+)\]", msg["subject"]) + if m: + full_msg = mailer.get(msg["id"]) + if full_msg: + reply_comment_email(full_msg) + mailer.delete(msg["id"]) @cron @@ -58,8 +53,8 @@ def submit_new_comment(): url=comment.url, comment=comment_text ) - site = Site.select().where(Site.id == Comment.site).get() # send email + site = Site.select().where(Site.id == Comment.site).get() subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") @@ -106,17 +101,13 @@ def reply_comment_email(data): # put a log to help fail2ban if message[:2].upper() == "SP": # SPAM - if comment_id in client_ips: + if comment.ip: logger.info( "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) ) else: logger.info("cannot identify SPAM source: %d" % comment_id) - # forget client IP - if comment_id in client_ips: - del client_ips[comment_id] - logger.info("discard comment: %d" % comment_id) comment.delete_instance() email_body = get_template("drop_comment").render(original=message) @@ -124,6 +115,7 @@ def reply_comment_email(data): else: # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.ip = None comment.save() logger.info("commit comment: %d" % comment_id) diff --git a/app/core/mailer.py b/app/core/mailer.py index b58e6e1..8e54651 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -1,15 +1,44 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import logging +import json +import requests +from conf import config + +logger = logging.getLogger(__name__) + def fetch(): - pass + mails = [] + r = requests.get(config.get(config.MAILER_URL) + "/mbox") + if r.status_code == 200: + logger.info("MAILER => " + str(r.json())) + payload = r.json() + if payload["count"] > 0: + mails = payload["emails"] + return mails -def send(email, subject, body): - pass +def get(id): + payload = None + r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + id) + if r.status_code == 200: + payload = r.json() + return payload -def delete(content): - # TODO delete mail - pass +def send(to_email, subject, message): + headers = {"Content-Type": "application/json; charset=utf-8"} + msg = {"to": to_email, "subject": subject, "content": message} + r = requests.post( + config.get(config.MAILER_URL) + "/mbox", data=json.dumps(msg), headers=headers + ) + if r.status_code in (200, 201): + logger.debug("Email for %s posted" % to_email) + else: + logger.warn("Cannot post email for %s" % to_email) + + +def delete(id): + requests.delete(config.get(config.MAILER_URL) + "/mbox/" + id) diff --git a/app/core/processor.py b/app/core/processor.py deleted file mode 100644 index 148924d..0000000 --- a/app/core/processor.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import logging -import re -import PyRSS2Gen -import markdown -import json -from datetime import datetime -from threading import Thread -from queue import Queue -from model.site import Site -from model.comment import Comment -from helper.hashing import md5 -from conf import config -from core import mailer - - -logger = logging.getLogger(__name__) -queue = Queue() -proc = None -env = None - -# keep client IP in memory until classified -client_ips = {} - - - - - -def get_email_metadata(message): - # retrieve metadata reader email from email body sent by admin - email = "" - m = re.search(r"email:\s(.+@.+\..+)", message) - if m: - email = m.group(1) - return email - - diff --git a/app/interface/api.py b/app/interface/api.py index 8aba222..c6f17f8 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -6,7 +6,6 @@ from flask import request, jsonify, abort from model.site import Site from model.comment import Comment from conf import config -from core import processor logger = logging.getLogger(__name__) app = config.flaskapp() From 8311053d0881419c547802cda07ad9a6b534564b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 15 Sep 2018 15:50:25 +0200 Subject: [PATCH 242/586] WIP --- app/core/cron.py | 30 +++++++++++------------------- app/core/mailer.py | 41 +++++++++++++++++++++++++++++++++++------ app/core/processor.py | 40 ---------------------------------------- app/interface/api.py | 1 - 4 files changed, 46 insertions(+), 66 deletions(-) delete mode 100644 app/core/processor.py diff --git a/app/core/cron.py b/app/core/cron.py index 932bf83..fcd8137 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -13,12 +13,10 @@ from model.comment import Site logger = logging.getLogger(__name__) -client_ips = {} - def cron(func): def wrapper(): - logger.debug("execute fun " + func) + logger.debug("execute CRON " + func.__name__) func() return wrapper @@ -27,16 +25,13 @@ def cron(func): @cron def fetch_mail_answers(): - msg = {} - - if msg["request"] == "new_mail": - reply_comment_email(msg["data"]) - mailer.delete(msg["data"]) - - # data = request.get_json() - # logger.debug(data) - - # processor.enqueue({'request': 'new_mail', 'data': data}) + for msg in mailer.fetch(): + m = re.search(r"\[(\d+)\:(\w+)\]", msg["subject"]) + if m: + full_msg = mailer.get(msg["id"]) + if full_msg: + reply_comment_email(full_msg) + mailer.delete(msg["id"]) @cron @@ -58,8 +53,8 @@ def submit_new_comment(): url=comment.url, comment=comment_text ) - site = Site.select().where(Site.id == Comment.site).get() # send email + site = Site.select().where(Site.id == Comment.site).get() subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") @@ -106,17 +101,13 @@ def reply_comment_email(data): # put a log to help fail2ban if message[:2].upper() == "SP": # SPAM - if comment_id in client_ips: + if comment.ip: logger.info( "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) ) else: logger.info("cannot identify SPAM source: %d" % comment_id) - # forget client IP - if comment_id in client_ips: - del client_ips[comment_id] - logger.info("discard comment: %d" % comment_id) comment.delete_instance() email_body = get_template("drop_comment").render(original=message) @@ -124,6 +115,7 @@ def reply_comment_email(data): else: # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.ip = None comment.save() logger.info("commit comment: %d" % comment_id) diff --git a/app/core/mailer.py b/app/core/mailer.py index b58e6e1..8e54651 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -1,15 +1,44 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import logging +import json +import requests +from conf import config + +logger = logging.getLogger(__name__) + def fetch(): - pass + mails = [] + r = requests.get(config.get(config.MAILER_URL) + "/mbox") + if r.status_code == 200: + logger.info("MAILER => " + str(r.json())) + payload = r.json() + if payload["count"] > 0: + mails = payload["emails"] + return mails -def send(email, subject, body): - pass +def get(id): + payload = None + r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + id) + if r.status_code == 200: + payload = r.json() + return payload -def delete(content): - # TODO delete mail - pass +def send(to_email, subject, message): + headers = {"Content-Type": "application/json; charset=utf-8"} + msg = {"to": to_email, "subject": subject, "content": message} + r = requests.post( + config.get(config.MAILER_URL) + "/mbox", data=json.dumps(msg), headers=headers + ) + if r.status_code in (200, 201): + logger.debug("Email for %s posted" % to_email) + else: + logger.warn("Cannot post email for %s" % to_email) + + +def delete(id): + requests.delete(config.get(config.MAILER_URL) + "/mbox/" + id) diff --git a/app/core/processor.py b/app/core/processor.py deleted file mode 100644 index 148924d..0000000 --- a/app/core/processor.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import logging -import re -import PyRSS2Gen -import markdown -import json -from datetime import datetime -from threading import Thread -from queue import Queue -from model.site import Site -from model.comment import Comment -from helper.hashing import md5 -from conf import config -from core import mailer - - -logger = logging.getLogger(__name__) -queue = Queue() -proc = None -env = None - -# keep client IP in memory until classified -client_ips = {} - - - - - -def get_email_metadata(message): - # retrieve metadata reader email from email body sent by admin - email = "" - m = re.search(r"email:\s(.+@.+\..+)", message) - if m: - email = m.group(1) - return email - - diff --git a/app/interface/api.py b/app/interface/api.py index 8aba222..c6f17f8 100644 --- a/app/interface/api.py +++ b/app/interface/api.py @@ -6,7 +6,6 @@ from flask import request, jsonify, abort from model.site import Site from model.comment import Comment from conf import config -from core import processor logger = logging.getLogger(__name__) app = config.flaskapp() From 564c27a00a3c8577329b0b88320eb4a77cafc6cb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Sep 2018 18:15:21 +0200 Subject: [PATCH 243/586] microservicified --- README.md | 2 +- app/core/cron.py | 22 ++++++++++++---------- app/core/mailer.py | 4 ++-- app/interface/form.py | 2 +- app/run.py | 4 ++++ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 72017aa..e04cad4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Stacosys can be hosted on the same server or on a different server than the blog Python 3.7 -pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig +pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig requests ### Ways of improvement diff --git a/app/core/cron.py b/app/core/cron.py index fcd8137..b9dbdb6 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -26,14 +26,11 @@ def cron(func): def fetch_mail_answers(): for msg in mailer.fetch(): - m = re.search(r"\[(\d+)\:(\w+)\]", msg["subject"]) - if m: + if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg["subject"], re.DOTALL): full_msg = mailer.get(msg["id"]) - if full_msg: - reply_comment_email(full_msg) + if full_msg and reply_comment_email(full_msg['email']): mailer.delete(msg["id"]) - @cron def submit_new_comment(): @@ -42,10 +39,10 @@ def submit_new_comment(): comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, - "date: %s" % comment.create, + "date: %s" % comment.created, "url: %s" % comment.url, "", - "%s" % comment.message, + "%s" % comment.content, "", ) comment_text = "\n".join(comment_list) @@ -54,11 +51,15 @@ def submit_new_comment(): ) # send email - site = Site.select().where(Site.id == Comment.site).get() + site = Site.get(Site.id == comment.site) subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") + # update comment + comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + def reply_comment_email(data): @@ -82,7 +83,7 @@ def reply_comment_email(data): comment = Comment.select().where(Comment.id == comment_id).get() except: logger.warn("unknown comment %d" % comment_id) - return + return True if comment.published: logger.warn("ignore already published email. token %d" % comment_id) @@ -103,7 +104,7 @@ def reply_comment_email(data): if message[:2].upper() == "SP": # SPAM if comment.ip: logger.info( - "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + "SPAM comment from %s: %d" % (comment.ip, comment_id) ) else: logger.info("cannot identify SPAM source: %d" % comment_id) @@ -126,3 +127,4 @@ def reply_comment_email(data): email_body = get_template("approve_comment").render(original=message) mailer.send(from_email, "Re: " + subject, email_body) + return True \ No newline at end of file diff --git a/app/core/mailer.py b/app/core/mailer.py index 8e54651..3697c49 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -22,7 +22,7 @@ def fetch(): def get(id): payload = None - r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + id) + r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + str(id)) if r.status_code == 200: payload = r.json() return payload @@ -41,4 +41,4 @@ def send(to_email, subject, message): def delete(id): - requests.delete(config.get(config.MAILER_URL) + "/mbox/" + id) + requests.delete(config.get(config.MAILER_URL) + "/mbox/" + str(id)) diff --git a/app/interface/form.py b/app/interface/form.py index 00cc149..42e04ad 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -42,7 +42,7 @@ def new_form_comment(): url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() - author_site = data.get("site", "").to_lower().strip() + author_site = data.get("site", "").lower().strip() if author_site and author_site[:4] != "http": author_site = "http://" + author_site message = data.get("message", "") diff --git a/app/run.py b/app/run.py index c57aa3a..c171eb6 100644 --- a/app/run.py +++ b/app/run.py @@ -26,6 +26,10 @@ class JobConfig(object): JOBS = [] + SCHEDULER_EXECUTORS = { + 'default': {'type': 'threadpool', 'max_workers': 20} + } + def __init__(self, mail_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { From ec86069ec734f920244f12cd815ae5cd82f86dba Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 15 Sep 2018 18:15:21 +0200 Subject: [PATCH 244/586] microservicified --- README.md | 2 +- app/core/cron.py | 22 ++++++++++++---------- app/core/mailer.py | 4 ++-- app/interface/form.py | 2 +- app/run.py | 4 ++++ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 72017aa..e04cad4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Stacosys can be hosted on the same server or on a different server than the blog Python 3.7 -pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig +pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig requests ### Ways of improvement diff --git a/app/core/cron.py b/app/core/cron.py index fcd8137..b9dbdb6 100644 --- a/app/core/cron.py +++ b/app/core/cron.py @@ -26,14 +26,11 @@ def cron(func): def fetch_mail_answers(): for msg in mailer.fetch(): - m = re.search(r"\[(\d+)\:(\w+)\]", msg["subject"]) - if m: + if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg["subject"], re.DOTALL): full_msg = mailer.get(msg["id"]) - if full_msg: - reply_comment_email(full_msg) + if full_msg and reply_comment_email(full_msg['email']): mailer.delete(msg["id"]) - @cron def submit_new_comment(): @@ -42,10 +39,10 @@ def submit_new_comment(): comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, - "date: %s" % comment.create, + "date: %s" % comment.created, "url: %s" % comment.url, "", - "%s" % comment.message, + "%s" % comment.content, "", ) comment_text = "\n".join(comment_list) @@ -54,11 +51,15 @@ def submit_new_comment(): ) # send email - site = Site.select().where(Site.id == Comment.site).get() + site = Site.get(Site.id == comment.site) subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") + # update comment + comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + def reply_comment_email(data): @@ -82,7 +83,7 @@ def reply_comment_email(data): comment = Comment.select().where(Comment.id == comment_id).get() except: logger.warn("unknown comment %d" % comment_id) - return + return True if comment.published: logger.warn("ignore already published email. token %d" % comment_id) @@ -103,7 +104,7 @@ def reply_comment_email(data): if message[:2].upper() == "SP": # SPAM if comment.ip: logger.info( - "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + "SPAM comment from %s: %d" % (comment.ip, comment_id) ) else: logger.info("cannot identify SPAM source: %d" % comment_id) @@ -126,3 +127,4 @@ def reply_comment_email(data): email_body = get_template("approve_comment").render(original=message) mailer.send(from_email, "Re: " + subject, email_body) + return True \ No newline at end of file diff --git a/app/core/mailer.py b/app/core/mailer.py index 8e54651..3697c49 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -22,7 +22,7 @@ def fetch(): def get(id): payload = None - r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + id) + r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + str(id)) if r.status_code == 200: payload = r.json() return payload @@ -41,4 +41,4 @@ def send(to_email, subject, message): def delete(id): - requests.delete(config.get(config.MAILER_URL) + "/mbox/" + id) + requests.delete(config.get(config.MAILER_URL) + "/mbox/" + str(id)) diff --git a/app/interface/form.py b/app/interface/form.py index 00cc149..42e04ad 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -42,7 +42,7 @@ def new_form_comment(): url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() - author_site = data.get("site", "").to_lower().strip() + author_site = data.get("site", "").lower().strip() if author_site and author_site[:4] != "http": author_site = "http://" + author_site message = data.get("message", "") diff --git a/app/run.py b/app/run.py index c57aa3a..c171eb6 100644 --- a/app/run.py +++ b/app/run.py @@ -26,6 +26,10 @@ class JobConfig(object): JOBS = [] + SCHEDULER_EXECUTORS = { + 'default': {'type': 'threadpool', 'max_workers': 20} + } + def __init__(self, mail_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { From fdc5d18b49bbc7f803f6dbcee6f8506b52cdef88 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Sep 2018 19:03:57 +0200 Subject: [PATCH 245/586] config --- config.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.ini b/config.ini index bd26c6a..b9be10c 100755 --- a/config.ini +++ b/config.ini @@ -18,5 +18,5 @@ proto = http file = comments.xml [mail] -fetch_polling = 15 +fetch_polling = 30 mailer_url = http://localhost:8000 From 5289cc3698c052d2a43b9cab6d6054060ad08245 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 15 Sep 2018 19:03:57 +0200 Subject: [PATCH 246/586] config --- config.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.ini b/config.ini index bd26c6a..b9be10c 100755 --- a/config.ini +++ b/config.ini @@ -18,5 +18,5 @@ proto = http file = comments.xml [mail] -fetch_polling = 15 +fetch_polling = 30 mailer_url = http://localhost:8000 From 733588d733306e655a60f167e73545510a8d2bc6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 30 Sep 2018 18:24:25 +0200 Subject: [PATCH 247/586] logging --- app/core/mailer.py | 1 - app/run.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/mailer.py b/app/core/mailer.py index 3697c49..9cacebc 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -13,7 +13,6 @@ def fetch(): mails = [] r = requests.get(config.get(config.MAILER_URL) + "/mbox") if r.status_code == 200: - logger.info("MAILER => " + str(r.json())) payload = r.json() if payload["count"] > 0: mails = payload["emails"] diff --git a/app/run.py b/app/run.py index c171eb6..0acfad4 100644 --- a/app/run.py +++ b/app/run.py @@ -57,6 +57,7 @@ def stacosys_server(config_pathname): logger = logging.getLogger(__name__) configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING + logging.getLogger("apscheduler.executors").level = logging.WARNING # initialize database from core import database From 21dbce748861fac5daa077a50cbf1d3a4fec0267 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 30 Sep 2018 18:24:25 +0200 Subject: [PATCH 248/586] logging --- app/core/mailer.py | 1 - app/run.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/mailer.py b/app/core/mailer.py index 3697c49..9cacebc 100644 --- a/app/core/mailer.py +++ b/app/core/mailer.py @@ -13,7 +13,6 @@ def fetch(): mails = [] r = requests.get(config.get(config.MAILER_URL) + "/mbox") if r.status_code == 200: - logger.info("MAILER => " + str(r.json())) payload = r.json() if payload["count"] > 0: mails = payload["emails"] diff --git a/app/run.py b/app/run.py index c171eb6..0acfad4 100644 --- a/app/run.py +++ b/app/run.py @@ -57,6 +57,7 @@ def stacosys_server(config_pathname): logger = logging.getLogger(__name__) configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING + logging.getLogger("apscheduler.executors").level = logging.WARNING # initialize database from core import database From 2060c883cb736aaa273e57eb2ee6b6308a620570 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 27 Oct 2018 18:10:28 +0200 Subject: [PATCH 249/586] argparse --- app/run.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/run.py b/app/run.py index 0acfad4..60e6c30 100644 --- a/app/run.py +++ b/app/run.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import argparse import os import logging -from clize import Clize, run from flask import Flask from flask_apscheduler import APScheduler from conf import config @@ -26,9 +26,7 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = { - 'default': {'type': 'threadpool', 'max_workers': 20} - } + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}} def __init__(self, mail_polling_seconds, new_comment_polling_seconds): self.JOBS = [ @@ -47,7 +45,6 @@ class JobConfig(object): ] -@Clize def stacosys_server(config_pathname): app = Flask(__name__) @@ -97,4 +94,7 @@ def stacosys_server(config_pathname): if __name__ == "__main__": - run(stacosys_server) + parser = argparse.ArgumentParser() + parser.add_argument("config", help="config path name") + args = parser.parse_args() + stacosys_server(args.config) From 9f4d778eb21cdd9e5a42b69e5c2086c3bb58c358 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 27 Oct 2018 18:10:28 +0200 Subject: [PATCH 250/586] argparse --- app/run.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/run.py b/app/run.py index 0acfad4..60e6c30 100644 --- a/app/run.py +++ b/app/run.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import argparse import os import logging -from clize import Clize, run from flask import Flask from flask_apscheduler import APScheduler from conf import config @@ -26,9 +26,7 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = { - 'default': {'type': 'threadpool', 'max_workers': 20} - } + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}} def __init__(self, mail_polling_seconds, new_comment_polling_seconds): self.JOBS = [ @@ -47,7 +45,6 @@ class JobConfig(object): ] -@Clize def stacosys_server(config_pathname): app = Flask(__name__) @@ -97,4 +94,7 @@ def stacosys_server(config_pathname): if __name__ == "__main__": - run(stacosys_server) + parser = argparse.ArgumentParser() + parser.add_argument("config", help="config path name") + args = parser.parse_args() + stacosys_server(args.config) From e992fef16f33c73d03066d77b6dd00a81030cf54 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 25 Aug 2019 18:37:41 +0200 Subject: [PATCH 251/586] update requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e676850..a9df345 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ MarkupSafe==1.0 mccabe==0.6.1 od==1.0 peewee==3.6.4 +profig==0.4.1 pylint==2.1.1 PyRSS2Gen==1.1 python-dateutil==2.7.3 From 1c75ecc9a1101f3cb703cce1b8ab883980badac3 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 25 Aug 2019 18:37:41 +0200 Subject: [PATCH 252/586] update requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e676850..a9df345 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ MarkupSafe==1.0 mccabe==0.6.1 od==1.0 peewee==3.6.4 +profig==0.4.1 pylint==2.1.1 PyRSS2Gen==1.1 python-dateutil==2.7.3 From 37bd3b81438bc19041b9ab8898291e46233995eb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Aug 2019 17:16:34 +0200 Subject: [PATCH 253/586] captcha --- app/interface/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/interface/form.py b/app/interface/form.py index 42e04ad..012afcb 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -34,7 +34,7 @@ def new_form_comment(): abort(400) # honeypot for spammers - captcha = data.get("captcha", "") + captcha = data.get("remarque", "") if captcha: logger.warn("discard spam: data %s" % data) abort(400) From 85fe290e838d328e051f5e04df0dd859c421bf95 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 31 Aug 2019 17:16:34 +0200 Subject: [PATCH 254/586] captcha --- app/interface/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/interface/form.py b/app/interface/form.py index 42e04ad..012afcb 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -34,7 +34,7 @@ def new_form_comment(): abort(400) # honeypot for spammers - captcha = data.get("captcha", "") + captcha = data.get("remarque", "") if captcha: logger.warn("discard spam: data %s" % data) abort(400) From cd45edced5cd1b6b6cd0ea99d1390678d4694ebf Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 1 Sep 2019 15:30:50 +0200 Subject: [PATCH 255/586] trace --- app/interface/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/interface/form.py b/app/interface/form.py index 012afcb..e4d30e3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -24,7 +24,7 @@ def new_form_comment(): ip = request.headers["X-Forwarded-For"] # log - logger.info(data) + logger.info("form data " + str(data)) # validate token: retrieve site entity token = data.get("token", "") From c2f2e9ab8946850023e626cb3ed54eab7fa404a6 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 1 Sep 2019 15:30:50 +0200 Subject: [PATCH 256/586] trace --- app/interface/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/interface/form.py b/app/interface/form.py index 012afcb..e4d30e3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -24,7 +24,7 @@ def new_form_comment(): ip = request.headers["X-Forwarded-For"] # log - logger.info(data) + logger.info("form data " + str(data)) # validate token: retrieve site entity token = data.get("token", "") From c1e18bf5cee8403060d831b7c9571d23bb2d80a4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 1 Sep 2019 15:50:05 +0200 Subject: [PATCH 257/586] anti-spam --- app/interface/form.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/interface/form.py b/app/interface/form.py index e4d30e3..14a32d3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -17,15 +17,13 @@ def new_form_comment(): try: data = request.form + logger.info("form data " + str(data)) # add client IP if provided by HTTP proxy ip = "" if "X-Forwarded-For" in request.headers: ip = request.headers["X-Forwarded-For"] - # log - logger.info("form data " + str(data)) - # validate token: retrieve site entity token = data.get("token", "") site = Site.select().where(Site.token == token).get() @@ -39,7 +37,7 @@ def new_form_comment(): logger.warn("discard spam: data %s" % data) abort(400) - url = data.get("url", "") + url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() author_site = data.get("site", "").lower().strip() @@ -47,9 +45,14 @@ def new_form_comment(): author_site = "http://" + author_site message = data.get("message", "") - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # anti-spam again + if not url or not author_name or not message: + logger.warn("empty field: data %s" % data) + abort(400) + check_form_data(data) # add a row to Comment table + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( site=site, url=url, @@ -69,3 +72,13 @@ def new_form_comment(): abort(400) return redirect("/redirect/", code=302) + +def check_form_data(data): + fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + d = data.to_dict() + for field in fields: + if field in d: + del d[field] + if d: + logger.warn("additional field: data %s" % data) + abort(400) \ No newline at end of file From 5fab9cae2f9775419bbd9fddd26a3110d36270a4 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 1 Sep 2019 15:50:05 +0200 Subject: [PATCH 258/586] anti-spam --- app/interface/form.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/interface/form.py b/app/interface/form.py index e4d30e3..14a32d3 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -17,15 +17,13 @@ def new_form_comment(): try: data = request.form + logger.info("form data " + str(data)) # add client IP if provided by HTTP proxy ip = "" if "X-Forwarded-For" in request.headers: ip = request.headers["X-Forwarded-For"] - # log - logger.info("form data " + str(data)) - # validate token: retrieve site entity token = data.get("token", "") site = Site.select().where(Site.token == token).get() @@ -39,7 +37,7 @@ def new_form_comment(): logger.warn("discard spam: data %s" % data) abort(400) - url = data.get("url", "") + url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() author_site = data.get("site", "").lower().strip() @@ -47,9 +45,14 @@ def new_form_comment(): author_site = "http://" + author_site message = data.get("message", "") - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # anti-spam again + if not url or not author_name or not message: + logger.warn("empty field: data %s" % data) + abort(400) + check_form_data(data) # add a row to Comment table + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( site=site, url=url, @@ -69,3 +72,13 @@ def new_form_comment(): abort(400) return redirect("/redirect/", code=302) + +def check_form_data(data): + fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + d = data.to_dict() + for field in fields: + if field in d: + del d[field] + if d: + logger.warn("additional field: data %s" % data) + abort(400) \ No newline at end of file From 9864d39df227e9905cc5335c2983b1aa2ae4f607 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Dec 2019 17:02:25 +0100 Subject: [PATCH 259/586] Stacosys 1.1.0 - Poetry --- app/conf/__init__.py => README.rst | 0 config.json | 20 - poetry.lock | 549 ++++++++++++++++++ pyproject.toml | 23 + run.sh | 2 +- stacosys/__init__.py | 1 + {app/core => stacosys/conf}/__init__.py | 0 {app => stacosys}/conf/config.py | 0 {app/interface => stacosys/core}/__init__.py | 0 {app => stacosys}/core/cron.py | 0 {app => stacosys}/core/database.py | 0 {app => stacosys}/core/mailer.py | 0 {app => stacosys}/core/rss.py | 0 {app => stacosys}/core/templater.py | 0 {app => stacosys}/helper/hashing.py | 0 stacosys/interface/__init__.py | 0 {app => stacosys}/interface/api.py | 0 {app => stacosys}/interface/form.py | 0 {app => stacosys}/model/comment.py | 0 {app => stacosys}/model/site.py | 0 {app => stacosys}/run.py | 0 .../templates/en/approve_comment.tpl | 0 .../templates/en/drop_comment.tpl | 0 .../templates/en/new_comment.tpl | 0 .../templates/en/notify_message.tpl | 0 .../templates/en/rss_title_message.tpl | 0 .../templates/fr/approve_comment.tpl | 0 .../templates/fr/drop_comment.tpl | 0 .../templates/fr/new_comment.tpl | 0 .../templates/fr/notify_message.tpl | 0 .../templates/fr/rss_title_message.tpl | 0 tests/__init__.py | 0 tests/test_stacosys.py | 5 + tools/pecosys2stacosys.py | 114 ---- 34 files changed, 579 insertions(+), 135 deletions(-) rename app/conf/__init__.py => README.rst (100%) delete mode 100755 config.json create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 stacosys/__init__.py rename {app/core => stacosys/conf}/__init__.py (100%) rename {app => stacosys}/conf/config.py (100%) rename {app/interface => stacosys/core}/__init__.py (100%) rename {app => stacosys}/core/cron.py (100%) rename {app => stacosys}/core/database.py (100%) rename {app => stacosys}/core/mailer.py (100%) rename {app => stacosys}/core/rss.py (100%) rename {app => stacosys}/core/templater.py (100%) rename {app => stacosys}/helper/hashing.py (100%) create mode 100644 stacosys/interface/__init__.py rename {app => stacosys}/interface/api.py (100%) rename {app => stacosys}/interface/form.py (100%) rename {app => stacosys}/model/comment.py (100%) rename {app => stacosys}/model/site.py (100%) rename {app => stacosys}/run.py (100%) rename {app => stacosys}/templates/en/approve_comment.tpl (100%) rename {app => stacosys}/templates/en/drop_comment.tpl (100%) rename {app => stacosys}/templates/en/new_comment.tpl (100%) rename {app => stacosys}/templates/en/notify_message.tpl (100%) rename {app => stacosys}/templates/en/rss_title_message.tpl (100%) rename {app => stacosys}/templates/fr/approve_comment.tpl (100%) rename {app => stacosys}/templates/fr/drop_comment.tpl (100%) rename {app => stacosys}/templates/fr/new_comment.tpl (100%) rename {app => stacosys}/templates/fr/notify_message.tpl (100%) rename {app => stacosys}/templates/fr/rss_title_message.tpl (100%) create mode 100644 tests/__init__.py create mode 100644 tests/test_stacosys.py delete mode 100644 tools/pecosys2stacosys.py diff --git a/app/conf/__init__.py b/README.rst similarity index 100% rename from app/conf/__init__.py rename to README.rst diff --git a/config.json b/config.json deleted file mode 100755 index 5abcdfc..0000000 --- a/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "general" : { - "debug": false, - "lang": "fr", - "db_url": "sqlite:///db.sqlite" - }, - "http": { - "root_url": "http://localhost:8100", - "host": "0.0.0.0", - "port": 8100 - }, - "security": { - "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", - "secret": "Uqca5Kc8xuU6THz9" - }, - "rss": { - "proto": "http", - "file": "comments.xml" - } -} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ff03a8b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,549 @@ +[[package]] +category = "main" +description = "In-process task scheduler with Cron-like capabilities" +name = "apscheduler" +optional = false +python-versions = "*" +version = "3.6.3" + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=1.2" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=2.8)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2019.11.28" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "7.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "A simple framework for building complex web applications." +name = "flask" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.1.1" + +[package.dependencies] +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" +click = ">=5.1" +itsdangerous = ">=0.24" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + +[[package]] +category = "main" +description = "Adds APScheduler support to Flask" +name = "flask-apscheduler" +optional = false +python-versions = "*" +version = "1.11.0" + +[package.dependencies] +apscheduler = ">=3.2.0" +flask = ">=0.10.1" +python-dateutil = ">=2.4.2" + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.3.0" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = "*" +version = "2.10.3" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +category = "main" +description = "Python implementation of Markdown." +name = "markdown" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "3.1.1" + +[package.dependencies] +setuptools = ">=36" + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.0.2" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.2" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "a little orm" +name = "peewee" +optional = false +python-versions = "*" +version = "3.13.1" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "main" +description = "A configuration library." +name = "profig" +optional = false +python-versions = "*" +version = "0.5.1" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.1" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.6" + +[[package]] +category = "main" +description = "Generate RSS2 using a Python data structure" +name = "pyrss2gen" +optional = false +python-versions = "*" +version = "1.1" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.3.2" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2019.3" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.22.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.9" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "1.13.0" + +[[package]] +category = "main" +description = "tzinfo object for the local timezone" +name = "tzlocal" +optional = false +python-versions = "*" +version = "2.0.0" + +[package.dependencies] +pytz = "*" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +version = "1.25.7" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + +[[package]] +category = "main" +description = "The comprehensive WSGI web application library." +name = "werkzeug" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.16.0" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] +termcolor = ["termcolor"] +watchdog = ["watchdog"] + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=2.7" +version = "0.6.0" + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pathlib2", "contextlib2", "unittest2"] + +[metadata] +content-hash = "8325c84a729d400cdc782fcc797efd58a5a598eff2ebb9eeb3f0bf436ecf624b" +python-versions = "^3.7" + +[metadata.files] +apscheduler = [ + {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, + {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, +] +atomicwrites = [ + {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, + {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +certifi = [ + {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, + {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +click = [ + {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, + {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +flask = [ + {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, + {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, +] +flask-apscheduler = [ + {file = "Flask-APScheduler-1.11.0.tar.gz", hash = "sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0"}, +] +idna = [ + {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, + {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"}, + {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] +jinja2 = [ + {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, + {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, +] +markdown = [ + {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, + {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +more-itertools = [ + {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, + {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, +] +packaging = [ + {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, + {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, +] +peewee = [ + {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +profig = [ + {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pyparsing = [ + {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, + {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, +] +pyrss2gen = [ + {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, +] +pytest = [ + {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"}, + {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +requests = [ + {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, + {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, +] +six = [ + {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, + {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, +] +tzlocal = [ + {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, + {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, +] +urllib3 = [ + {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, + {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, +] +wcwidth = [ + {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, + {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, +] +werkzeug = [ + {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, + {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, +] +zipp = [ + {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, + {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1a490a6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "stacosys" +version = "1.1.0" +description = "STAtic COmmenting SYStem" +authors = ["Yax"] + +[tool.poetry.dependencies] +python = "^3.7" +apscheduler = "^3.6.3" +flask = "^1.1.1" +peewee = "^3.13.1" +pyrss2gen = "^1.1" +profig = "^0.5.1" +markdown = "^3.1.1" +flask_apscheduler = "^1.11.0" +requests = "^2.22.0" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/run.sh b/run.sh index a3b0364..24b53b5 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/run.py "$@" +python stacosys/run.py "$@" diff --git a/stacosys/__init__.py b/stacosys/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/stacosys/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/app/core/__init__.py b/stacosys/conf/__init__.py similarity index 100% rename from app/core/__init__.py rename to stacosys/conf/__init__.py diff --git a/app/conf/config.py b/stacosys/conf/config.py similarity index 100% rename from app/conf/config.py rename to stacosys/conf/config.py diff --git a/app/interface/__init__.py b/stacosys/core/__init__.py similarity index 100% rename from app/interface/__init__.py rename to stacosys/core/__init__.py diff --git a/app/core/cron.py b/stacosys/core/cron.py similarity index 100% rename from app/core/cron.py rename to stacosys/core/cron.py diff --git a/app/core/database.py b/stacosys/core/database.py similarity index 100% rename from app/core/database.py rename to stacosys/core/database.py diff --git a/app/core/mailer.py b/stacosys/core/mailer.py similarity index 100% rename from app/core/mailer.py rename to stacosys/core/mailer.py diff --git a/app/core/rss.py b/stacosys/core/rss.py similarity index 100% rename from app/core/rss.py rename to stacosys/core/rss.py diff --git a/app/core/templater.py b/stacosys/core/templater.py similarity index 100% rename from app/core/templater.py rename to stacosys/core/templater.py diff --git a/app/helper/hashing.py b/stacosys/helper/hashing.py similarity index 100% rename from app/helper/hashing.py rename to stacosys/helper/hashing.py diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/interface/api.py b/stacosys/interface/api.py similarity index 100% rename from app/interface/api.py rename to stacosys/interface/api.py diff --git a/app/interface/form.py b/stacosys/interface/form.py similarity index 100% rename from app/interface/form.py rename to stacosys/interface/form.py diff --git a/app/model/comment.py b/stacosys/model/comment.py similarity index 100% rename from app/model/comment.py rename to stacosys/model/comment.py diff --git a/app/model/site.py b/stacosys/model/site.py similarity index 100% rename from app/model/site.py rename to stacosys/model/site.py diff --git a/app/run.py b/stacosys/run.py similarity index 100% rename from app/run.py rename to stacosys/run.py diff --git a/app/templates/en/approve_comment.tpl b/stacosys/templates/en/approve_comment.tpl similarity index 100% rename from app/templates/en/approve_comment.tpl rename to stacosys/templates/en/approve_comment.tpl diff --git a/app/templates/en/drop_comment.tpl b/stacosys/templates/en/drop_comment.tpl similarity index 100% rename from app/templates/en/drop_comment.tpl rename to stacosys/templates/en/drop_comment.tpl diff --git a/app/templates/en/new_comment.tpl b/stacosys/templates/en/new_comment.tpl similarity index 100% rename from app/templates/en/new_comment.tpl rename to stacosys/templates/en/new_comment.tpl diff --git a/app/templates/en/notify_message.tpl b/stacosys/templates/en/notify_message.tpl similarity index 100% rename from app/templates/en/notify_message.tpl rename to stacosys/templates/en/notify_message.tpl diff --git a/app/templates/en/rss_title_message.tpl b/stacosys/templates/en/rss_title_message.tpl similarity index 100% rename from app/templates/en/rss_title_message.tpl rename to stacosys/templates/en/rss_title_message.tpl diff --git a/app/templates/fr/approve_comment.tpl b/stacosys/templates/fr/approve_comment.tpl similarity index 100% rename from app/templates/fr/approve_comment.tpl rename to stacosys/templates/fr/approve_comment.tpl diff --git a/app/templates/fr/drop_comment.tpl b/stacosys/templates/fr/drop_comment.tpl similarity index 100% rename from app/templates/fr/drop_comment.tpl rename to stacosys/templates/fr/drop_comment.tpl diff --git a/app/templates/fr/new_comment.tpl b/stacosys/templates/fr/new_comment.tpl similarity index 100% rename from app/templates/fr/new_comment.tpl rename to stacosys/templates/fr/new_comment.tpl diff --git a/app/templates/fr/notify_message.tpl b/stacosys/templates/fr/notify_message.tpl similarity index 100% rename from app/templates/fr/notify_message.tpl rename to stacosys/templates/fr/notify_message.tpl diff --git a/app/templates/fr/rss_title_message.tpl b/stacosys/templates/fr/rss_title_message.tpl similarity index 100% rename from app/templates/fr/rss_title_message.tpl rename to stacosys/templates/fr/rss_title_message.tpl diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py new file mode 100644 index 0000000..f0d2e64 --- /dev/null +++ b/tests/test_stacosys.py @@ -0,0 +1,5 @@ +from stacosys import __version__ + + +def test_version(): + assert __version__ == '0.1.0' diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py deleted file mode 100644 index 0d5d26b..0000000 --- a/tools/pecosys2stacosys.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -import sys -import os -import re -import logging -import datetime -from clize import clize, run - -# add necessary directories to PATH -current_path = os.path.realpath('.') -parent_path = os.path.abspath(os.path.join(current_path, '..')) -paths = [current_path, parent_path] -for path in paths: - if path not in sys.path: - sys.path.insert(0, path) - -# import database models -from app.services.database import provide_db -from app.helpers.hashing import salt -from app.models.site import Site -from app.models.comment import Comment - -# configure logging -level = logging.DEBUG -logger = logging.getLogger(__name__) -logger.setLevel(level) -ch = logging.StreamHandler() -ch.setLevel(level) -formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') -ch.setFormatter(formatter) -logger.addHandler(ch) - -# regex -regex = re.compile(r"(\w+):\s*(.*)") - -def remove_from_string(line, start): - if line[:len(start)] == start: - line = line[len(start):].strip() - return line - -def convert_comment(db, site, root_url, filename): - logger.info('convert %s' % filename) - d = {} - content = '' - with open(filename) as f: - for line in f: - match = regex.match(line) - if match: - d[match.group(1)] = match.group(2) - else: - break - is_header = True - for line in f: - if is_header: - if line.strip(): - is_header = False - else: - continue - content = content + line - - # create DB record - comment = Comment(site=site, author_name=d['author'], content=content) - if 'email' in d: - comment.author_email = d['email'].strip() - if 'site' in d: - comment.author_site = d['site'].strip() - if 'url' in d: - url = remove_from_string(d['url'], 'https://') - url = remove_from_string(url, 'http://') - comment.url = remove_from_string(url, root_url) - # comment.url = remove_from_string(url, '/') - # else: - # comment.url = d['article'] - if 'date' in d: - pub = datetime.datetime.strptime(d['date'], '%Y-%m-%d %H:%M:%S') - comment.created = pub - comment.published = pub - comment.save() - - -@provide_db -def convert(db, site_name, url, admin_email, comment_dir): - - # create DB tables if needed - db.create_tables([Site, Comment], safe=True) - - # delete site record - try: - site = Site.select().where(Site.name == site_name).get() - site.delete_instance(recursive=True) - except Site.DoesNotExist: - pass - - site = Site.create(name=site_name, url=url, token=salt(url), - admin_email=admin_email) - - for dirpath, dirs, files in os.walk(comment_dir): - for filename in files: - if filename.endswith(('.md',)): - comment_file = '/'.join([dirpath, filename]) - convert_comment(db, site, url, comment_file) - else: - logger.warn('ignore file %s' % filename) - - -@clize -def pecosys2stacosys(site, url, admin_email, comment_dir): - convert(site, url, admin_email, comment_dir) - - -if __name__ == '__main__': - run(pecosys2stacosys) From 5261a325f3c2b4ca586c47014f048c2278c9757b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 28 Dec 2019 17:02:25 +0100 Subject: [PATCH 260/586] Stacosys 1.1.0 - Poetry --- app/conf/__init__.py => README.rst | 0 config.json | 20 - poetry.lock | 549 ++++++++++++++++++ pyproject.toml | 23 + run.sh | 2 +- stacosys/__init__.py | 1 + {app/core => stacosys/conf}/__init__.py | 0 {app => stacosys}/conf/config.py | 0 {app/interface => stacosys/core}/__init__.py | 0 {app => stacosys}/core/cron.py | 0 {app => stacosys}/core/database.py | 0 {app => stacosys}/core/mailer.py | 0 {app => stacosys}/core/rss.py | 0 {app => stacosys}/core/templater.py | 0 {app => stacosys}/helper/hashing.py | 0 stacosys/interface/__init__.py | 0 {app => stacosys}/interface/api.py | 0 {app => stacosys}/interface/form.py | 0 {app => stacosys}/model/comment.py | 0 {app => stacosys}/model/site.py | 0 {app => stacosys}/run.py | 0 .../templates/en/approve_comment.tpl | 0 .../templates/en/drop_comment.tpl | 0 .../templates/en/new_comment.tpl | 0 .../templates/en/notify_message.tpl | 0 .../templates/en/rss_title_message.tpl | 0 .../templates/fr/approve_comment.tpl | 0 .../templates/fr/drop_comment.tpl | 0 .../templates/fr/new_comment.tpl | 0 .../templates/fr/notify_message.tpl | 0 .../templates/fr/rss_title_message.tpl | 0 tests/__init__.py | 0 tests/test_stacosys.py | 5 + tools/pecosys2stacosys.py | 114 ---- 34 files changed, 579 insertions(+), 135 deletions(-) rename app/conf/__init__.py => README.rst (100%) delete mode 100755 config.json create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 stacosys/__init__.py rename {app/core => stacosys/conf}/__init__.py (100%) rename {app => stacosys}/conf/config.py (100%) rename {app/interface => stacosys/core}/__init__.py (100%) rename {app => stacosys}/core/cron.py (100%) rename {app => stacosys}/core/database.py (100%) rename {app => stacosys}/core/mailer.py (100%) rename {app => stacosys}/core/rss.py (100%) rename {app => stacosys}/core/templater.py (100%) rename {app => stacosys}/helper/hashing.py (100%) create mode 100644 stacosys/interface/__init__.py rename {app => stacosys}/interface/api.py (100%) rename {app => stacosys}/interface/form.py (100%) rename {app => stacosys}/model/comment.py (100%) rename {app => stacosys}/model/site.py (100%) rename {app => stacosys}/run.py (100%) rename {app => stacosys}/templates/en/approve_comment.tpl (100%) rename {app => stacosys}/templates/en/drop_comment.tpl (100%) rename {app => stacosys}/templates/en/new_comment.tpl (100%) rename {app => stacosys}/templates/en/notify_message.tpl (100%) rename {app => stacosys}/templates/en/rss_title_message.tpl (100%) rename {app => stacosys}/templates/fr/approve_comment.tpl (100%) rename {app => stacosys}/templates/fr/drop_comment.tpl (100%) rename {app => stacosys}/templates/fr/new_comment.tpl (100%) rename {app => stacosys}/templates/fr/notify_message.tpl (100%) rename {app => stacosys}/templates/fr/rss_title_message.tpl (100%) create mode 100644 tests/__init__.py create mode 100644 tests/test_stacosys.py delete mode 100644 tools/pecosys2stacosys.py diff --git a/app/conf/__init__.py b/README.rst similarity index 100% rename from app/conf/__init__.py rename to README.rst diff --git a/config.json b/config.json deleted file mode 100755 index 5abcdfc..0000000 --- a/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "general" : { - "debug": false, - "lang": "fr", - "db_url": "sqlite:///db.sqlite" - }, - "http": { - "root_url": "http://localhost:8100", - "host": "0.0.0.0", - "port": 8100 - }, - "security": { - "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", - "secret": "Uqca5Kc8xuU6THz9" - }, - "rss": { - "proto": "http", - "file": "comments.xml" - } -} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ff03a8b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,549 @@ +[[package]] +category = "main" +description = "In-process task scheduler with Cron-like capabilities" +name = "apscheduler" +optional = false +python-versions = "*" +version = "3.6.3" + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=1.2" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=2.8)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2019.11.28" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "7.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "A simple framework for building complex web applications." +name = "flask" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.1.1" + +[package.dependencies] +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" +click = ">=5.1" +itsdangerous = ">=0.24" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + +[[package]] +category = "main" +description = "Adds APScheduler support to Flask" +name = "flask-apscheduler" +optional = false +python-versions = "*" +version = "1.11.0" + +[package.dependencies] +apscheduler = ">=3.2.0" +flask = ">=0.10.1" +python-dateutil = ">=2.4.2" + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.3.0" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = "*" +version = "2.10.3" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +category = "main" +description = "Python implementation of Markdown." +name = "markdown" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "3.1.1" + +[package.dependencies] +setuptools = ">=36" + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.0.2" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.2" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "a little orm" +name = "peewee" +optional = false +python-versions = "*" +version = "3.13.1" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "main" +description = "A configuration library." +name = "profig" +optional = false +python-versions = "*" +version = "0.5.1" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.1" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.6" + +[[package]] +category = "main" +description = "Generate RSS2 using a Python data structure" +name = "pyrss2gen" +optional = false +python-versions = "*" +version = "1.1" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.3.2" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2019.3" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.22.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.9" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "1.13.0" + +[[package]] +category = "main" +description = "tzinfo object for the local timezone" +name = "tzlocal" +optional = false +python-versions = "*" +version = "2.0.0" + +[package.dependencies] +pytz = "*" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +version = "1.25.7" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + +[[package]] +category = "main" +description = "The comprehensive WSGI web application library." +name = "werkzeug" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.16.0" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] +termcolor = ["termcolor"] +watchdog = ["watchdog"] + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=2.7" +version = "0.6.0" + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pathlib2", "contextlib2", "unittest2"] + +[metadata] +content-hash = "8325c84a729d400cdc782fcc797efd58a5a598eff2ebb9eeb3f0bf436ecf624b" +python-versions = "^3.7" + +[metadata.files] +apscheduler = [ + {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, + {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, +] +atomicwrites = [ + {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, + {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +certifi = [ + {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, + {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +click = [ + {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, + {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +flask = [ + {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, + {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, +] +flask-apscheduler = [ + {file = "Flask-APScheduler-1.11.0.tar.gz", hash = "sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0"}, +] +idna = [ + {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, + {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"}, + {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] +jinja2 = [ + {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, + {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, +] +markdown = [ + {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, + {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +more-itertools = [ + {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, + {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, +] +packaging = [ + {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, + {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, +] +peewee = [ + {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +profig = [ + {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pyparsing = [ + {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, + {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, +] +pyrss2gen = [ + {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, +] +pytest = [ + {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"}, + {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +requests = [ + {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, + {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, +] +six = [ + {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, + {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, +] +tzlocal = [ + {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, + {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, +] +urllib3 = [ + {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, + {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, +] +wcwidth = [ + {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, + {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, +] +werkzeug = [ + {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, + {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, +] +zipp = [ + {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, + {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1a490a6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "stacosys" +version = "1.1.0" +description = "STAtic COmmenting SYStem" +authors = ["Yax"] + +[tool.poetry.dependencies] +python = "^3.7" +apscheduler = "^3.6.3" +flask = "^1.1.1" +peewee = "^3.13.1" +pyrss2gen = "^1.1" +profig = "^0.5.1" +markdown = "^3.1.1" +flask_apscheduler = "^1.11.0" +requests = "^2.22.0" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/run.sh b/run.sh index a3b0364..24b53b5 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python app/run.py "$@" +python stacosys/run.py "$@" diff --git a/stacosys/__init__.py b/stacosys/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/stacosys/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/app/core/__init__.py b/stacosys/conf/__init__.py similarity index 100% rename from app/core/__init__.py rename to stacosys/conf/__init__.py diff --git a/app/conf/config.py b/stacosys/conf/config.py similarity index 100% rename from app/conf/config.py rename to stacosys/conf/config.py diff --git a/app/interface/__init__.py b/stacosys/core/__init__.py similarity index 100% rename from app/interface/__init__.py rename to stacosys/core/__init__.py diff --git a/app/core/cron.py b/stacosys/core/cron.py similarity index 100% rename from app/core/cron.py rename to stacosys/core/cron.py diff --git a/app/core/database.py b/stacosys/core/database.py similarity index 100% rename from app/core/database.py rename to stacosys/core/database.py diff --git a/app/core/mailer.py b/stacosys/core/mailer.py similarity index 100% rename from app/core/mailer.py rename to stacosys/core/mailer.py diff --git a/app/core/rss.py b/stacosys/core/rss.py similarity index 100% rename from app/core/rss.py rename to stacosys/core/rss.py diff --git a/app/core/templater.py b/stacosys/core/templater.py similarity index 100% rename from app/core/templater.py rename to stacosys/core/templater.py diff --git a/app/helper/hashing.py b/stacosys/helper/hashing.py similarity index 100% rename from app/helper/hashing.py rename to stacosys/helper/hashing.py diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/interface/api.py b/stacosys/interface/api.py similarity index 100% rename from app/interface/api.py rename to stacosys/interface/api.py diff --git a/app/interface/form.py b/stacosys/interface/form.py similarity index 100% rename from app/interface/form.py rename to stacosys/interface/form.py diff --git a/app/model/comment.py b/stacosys/model/comment.py similarity index 100% rename from app/model/comment.py rename to stacosys/model/comment.py diff --git a/app/model/site.py b/stacosys/model/site.py similarity index 100% rename from app/model/site.py rename to stacosys/model/site.py diff --git a/app/run.py b/stacosys/run.py similarity index 100% rename from app/run.py rename to stacosys/run.py diff --git a/app/templates/en/approve_comment.tpl b/stacosys/templates/en/approve_comment.tpl similarity index 100% rename from app/templates/en/approve_comment.tpl rename to stacosys/templates/en/approve_comment.tpl diff --git a/app/templates/en/drop_comment.tpl b/stacosys/templates/en/drop_comment.tpl similarity index 100% rename from app/templates/en/drop_comment.tpl rename to stacosys/templates/en/drop_comment.tpl diff --git a/app/templates/en/new_comment.tpl b/stacosys/templates/en/new_comment.tpl similarity index 100% rename from app/templates/en/new_comment.tpl rename to stacosys/templates/en/new_comment.tpl diff --git a/app/templates/en/notify_message.tpl b/stacosys/templates/en/notify_message.tpl similarity index 100% rename from app/templates/en/notify_message.tpl rename to stacosys/templates/en/notify_message.tpl diff --git a/app/templates/en/rss_title_message.tpl b/stacosys/templates/en/rss_title_message.tpl similarity index 100% rename from app/templates/en/rss_title_message.tpl rename to stacosys/templates/en/rss_title_message.tpl diff --git a/app/templates/fr/approve_comment.tpl b/stacosys/templates/fr/approve_comment.tpl similarity index 100% rename from app/templates/fr/approve_comment.tpl rename to stacosys/templates/fr/approve_comment.tpl diff --git a/app/templates/fr/drop_comment.tpl b/stacosys/templates/fr/drop_comment.tpl similarity index 100% rename from app/templates/fr/drop_comment.tpl rename to stacosys/templates/fr/drop_comment.tpl diff --git a/app/templates/fr/new_comment.tpl b/stacosys/templates/fr/new_comment.tpl similarity index 100% rename from app/templates/fr/new_comment.tpl rename to stacosys/templates/fr/new_comment.tpl diff --git a/app/templates/fr/notify_message.tpl b/stacosys/templates/fr/notify_message.tpl similarity index 100% rename from app/templates/fr/notify_message.tpl rename to stacosys/templates/fr/notify_message.tpl diff --git a/app/templates/fr/rss_title_message.tpl b/stacosys/templates/fr/rss_title_message.tpl similarity index 100% rename from app/templates/fr/rss_title_message.tpl rename to stacosys/templates/fr/rss_title_message.tpl diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py new file mode 100644 index 0000000..f0d2e64 --- /dev/null +++ b/tests/test_stacosys.py @@ -0,0 +1,5 @@ +from stacosys import __version__ + + +def test_version(): + assert __version__ == '0.1.0' diff --git a/tools/pecosys2stacosys.py b/tools/pecosys2stacosys.py deleted file mode 100644 index 0d5d26b..0000000 --- a/tools/pecosys2stacosys.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -import sys -import os -import re -import logging -import datetime -from clize import clize, run - -# add necessary directories to PATH -current_path = os.path.realpath('.') -parent_path = os.path.abspath(os.path.join(current_path, '..')) -paths = [current_path, parent_path] -for path in paths: - if path not in sys.path: - sys.path.insert(0, path) - -# import database models -from app.services.database import provide_db -from app.helpers.hashing import salt -from app.models.site import Site -from app.models.comment import Comment - -# configure logging -level = logging.DEBUG -logger = logging.getLogger(__name__) -logger.setLevel(level) -ch = logging.StreamHandler() -ch.setLevel(level) -formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') -ch.setFormatter(formatter) -logger.addHandler(ch) - -# regex -regex = re.compile(r"(\w+):\s*(.*)") - -def remove_from_string(line, start): - if line[:len(start)] == start: - line = line[len(start):].strip() - return line - -def convert_comment(db, site, root_url, filename): - logger.info('convert %s' % filename) - d = {} - content = '' - with open(filename) as f: - for line in f: - match = regex.match(line) - if match: - d[match.group(1)] = match.group(2) - else: - break - is_header = True - for line in f: - if is_header: - if line.strip(): - is_header = False - else: - continue - content = content + line - - # create DB record - comment = Comment(site=site, author_name=d['author'], content=content) - if 'email' in d: - comment.author_email = d['email'].strip() - if 'site' in d: - comment.author_site = d['site'].strip() - if 'url' in d: - url = remove_from_string(d['url'], 'https://') - url = remove_from_string(url, 'http://') - comment.url = remove_from_string(url, root_url) - # comment.url = remove_from_string(url, '/') - # else: - # comment.url = d['article'] - if 'date' in d: - pub = datetime.datetime.strptime(d['date'], '%Y-%m-%d %H:%M:%S') - comment.created = pub - comment.published = pub - comment.save() - - -@provide_db -def convert(db, site_name, url, admin_email, comment_dir): - - # create DB tables if needed - db.create_tables([Site, Comment], safe=True) - - # delete site record - try: - site = Site.select().where(Site.name == site_name).get() - site.delete_instance(recursive=True) - except Site.DoesNotExist: - pass - - site = Site.create(name=site_name, url=url, token=salt(url), - admin_email=admin_email) - - for dirpath, dirs, files in os.walk(comment_dir): - for filename in files: - if filename.endswith(('.md',)): - comment_file = '/'.join([dirpath, filename]) - convert_comment(db, site, url, comment_file) - else: - logger.warn('ignore file %s' % filename) - - -@clize -def pecosys2stacosys(site, url, admin_email, comment_dir): - convert(site, url, admin_email, comment_dir) - - -if __name__ == '__main__': - run(pecosys2stacosys) From 0e427e08b1e95ec4719f9e4ac1c860de62349c34 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Dec 2019 17:19:45 +0100 Subject: [PATCH 261/586] fix pyproject readme --- README.rst | 0 pyproject.toml | 1 + requirements.txt | 32 -------------------------------- 3 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 README.rst delete mode 100644 requirements.txt diff --git a/README.rst b/README.rst deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index 1a490a6..e06fd3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "stacosys" version = "1.1.0" description = "STAtic COmmenting SYStem" authors = ["Yax"] +readme = "README.md" [tool.poetry.dependencies] python = "^3.7" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a9df345..0000000 --- a/requirements.txt +++ /dev/null @@ -1,32 +0,0 @@ -appdirs==1.4.3 -APScheduler==3.5.3 -astroid==2.0.4 -attrs==18.2.0 -black==18.6b4 -chardet==3.0.4 -click==6.7 -clize==4.0.3 -docutils==0.14 -Flask==1.0.2 -Flask-APScheduler==1.10.1 -isort==4.3.4 -itsdangerous==0.24 -Jinja2==2.10 -jsonschema==2.6.0 -lazy-object-proxy==1.3.1 -Markdown==2.6.11 -MarkupSafe==1.0 -mccabe==0.6.1 -od==1.0 -peewee==3.6.4 -profig==0.4.1 -pylint==2.1.1 -PyRSS2Gen==1.1 -python-dateutil==2.7.3 -pytz==2018.5 -sigtools==2.0.2 -six==1.11.0 -toml==0.9.4 -tzlocal==1.5.1 -Werkzeug==0.14.1 -wrapt==1.10.11 From d31bf56096960c0858b7e4aa358b7a21f48431dc Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 28 Dec 2019 17:19:45 +0100 Subject: [PATCH 262/586] fix pyproject readme --- README.rst | 0 pyproject.toml | 1 + requirements.txt | 32 -------------------------------- 3 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 README.rst delete mode 100644 requirements.txt diff --git a/README.rst b/README.rst deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index 1a490a6..e06fd3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "stacosys" version = "1.1.0" description = "STAtic COmmenting SYStem" authors = ["Yax"] +readme = "README.md" [tool.poetry.dependencies] python = "^3.7" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a9df345..0000000 --- a/requirements.txt +++ /dev/null @@ -1,32 +0,0 @@ -appdirs==1.4.3 -APScheduler==3.5.3 -astroid==2.0.4 -attrs==18.2.0 -black==18.6b4 -chardet==3.0.4 -click==6.7 -clize==4.0.3 -docutils==0.14 -Flask==1.0.2 -Flask-APScheduler==1.10.1 -isort==4.3.4 -itsdangerous==0.24 -Jinja2==2.10 -jsonschema==2.6.0 -lazy-object-proxy==1.3.1 -Markdown==2.6.11 -MarkupSafe==1.0 -mccabe==0.6.1 -od==1.0 -peewee==3.6.4 -profig==0.4.1 -pylint==2.1.1 -PyRSS2Gen==1.1 -python-dateutil==2.7.3 -pytz==2018.5 -sigtools==2.0.2 -six==1.11.0 -toml==0.9.4 -tzlocal==1.5.1 -Werkzeug==0.14.1 -wrapt==1.10.11 From fc958e7fb918e9f1701b21ec33beece143aa8661 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Mar 2020 17:57:13 +0100 Subject: [PATCH 263/586] clean-up code --- poetry.lock | 125 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + requirements.txt | 85 ++++++++++++++++++++++ stacosys/__init__.py | 1 - stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/core/cron.py | 23 ++---- stacosys/interface/__init__.py | 0 stacosys/interface/api.py | 94 ++++++++++--------------- stacosys/interface/form.py | 18 ++--- stacosys/model/comment.py | 18 +++-- 11 files changed, 274 insertions(+), 91 deletions(-) create mode 100644 requirements.txt delete mode 100644 stacosys/__init__.py delete mode 100644 stacosys/conf/__init__.py delete mode 100644 stacosys/core/__init__.py delete mode 100644 stacosys/interface/__init__.py diff --git a/poetry.lock b/poetry.lock index ff03a8b..0d8f68b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.3" + [[package]] category = "main" description = "In-process task scheduler with Cron-like capabilities" @@ -48,6 +56,26 @@ dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.int docs = ["sphinx", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "19.10b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=18.1.0" +click = ">=6.5" +pathspec = ">=0.6,<1" +regex = "*" +toml = ">=0.9.4" +typed-ast = ">=1.4.0" + +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] category = "main" description = "Python package for providing Mozilla's CA Bundle." @@ -201,6 +229,14 @@ version = "19.2" pyparsing = ">=2.0.2" six = "*" +[[package]] +category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.7.0" + [[package]] category = "main" description = "a little orm" @@ -301,6 +337,14 @@ optional = false python-versions = "*" version = "2019.3" +[[package]] +category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" +optional = false +python-versions = "*" +version = "2020.2.20" + [[package]] category = "main" description = "Python HTTP for Humans." @@ -327,6 +371,22 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" version = "1.13.0" +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.1" + [[package]] category = "main" description = "tzinfo object for the local timezone" @@ -389,10 +449,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "8325c84a729d400cdc782fcc797efd58a5a598eff2ebb9eeb3f0bf436ecf624b" +content-hash = "6270dbd1455ca926e89cdd874748cf8cee18c2ef5a10a05b6dc7f7100e2482d5" python-versions = "^3.7" [metadata.files] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] apscheduler = [ {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, @@ -405,6 +469,10 @@ attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, ] +black = [ + {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] certifi = [ {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, @@ -486,6 +554,10 @@ packaging = [ {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, ] +pathspec = [ + {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, + {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, +] peewee = [ {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, ] @@ -519,6 +591,29 @@ pytz = [ {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, ] +regex = [ + {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, + {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, + {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, + {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, + {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, + {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, + {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, + {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, + {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, +] requests = [ {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, @@ -527,6 +622,34 @@ six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, ] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] tzlocal = [ {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, diff --git a/pyproject.toml b/pyproject.toml index e06fd3d..8f856e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ requests = "^2.22.0" [tool.poetry.dev-dependencies] pytest = "^5.2" +black = {version = "^19.10b0", allow-prereleases = true} [build-system] requires = ["poetry>=0.12"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a049a0c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,85 @@ +apscheduler==3.6.3 \ + --hash=sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526 \ + --hash=sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f +chardet==3.0.4 \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae +click==7.0 \ + --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ + --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 +flask==1.1.1 \ + --hash=sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6 \ + --hash=sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52 +flask-apscheduler==1.11.0 \ + --hash=sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0 +idna==2.8 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 +itsdangerous==1.1.0 \ + --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ + --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 +jinja2==2.10.3 \ + --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \ + --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de +markdown==3.1.1 \ + --hash=sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c \ + --hash=sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a +markupsafe==1.1.1 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b +peewee==3.13.1 \ + --hash=sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde +profig==0.5.1 \ + --hash=sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6 +pyrss2gen==1.1 \ + --hash=sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be +requests==2.22.0 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +tzlocal==2.0.0 \ + --hash=sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048 \ + --hash=sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590 +urllib3==1.25.7 \ + --hash=sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293 \ + --hash=sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745 +werkzeug==0.16.0 \ + --hash=sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4 \ + --hash=sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7 diff --git a/stacosys/__init__.py b/stacosys/__init__.py deleted file mode 100644 index b794fd4..0000000 --- a/stacosys/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.1.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index b9dbdb6..69dc042 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -56,9 +56,8 @@ def submit_new_comment(): mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") - # update comment - comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() + # notify site admin and save notification datetime + comment.notify_site_admin() def reply_comment_email(data): @@ -98,26 +97,14 @@ def reply_comment_email(data): return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO", "SP"): - - # put a log to help fail2ban - if message[:2].upper() == "SP": # SPAM - if comment.ip: - logger.info( - "SPAM comment from %s: %d" % (comment.ip, comment_id) - ) - else: - logger.info("cannot identify SPAM source: %d" % comment_id) - + if message[:2].upper() in ("NO"): logger.info("discard comment: %d" % comment_id) comment.delete_instance() email_body = get_template("drop_comment").render(original=message) mailer.send(from_email, "Re: " + subject, email_body) else: - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.ip = None - comment.save() + # save publishing datetime + comment.publish() logger.info("commit comment: %d" % comment_id) # rebuild RSS diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index c6f17f8..23270f5 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -10,83 +10,67 @@ from conf import config logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/ping", methods=['GET']) + +@app.route("/ping", methods=["GET"]) def ping(): return "OK" -@app.route("/comments", methods=['GET']) +@app.route("/comments", methods=["GET"]) def query_comments(): comments = [] try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") - logger.info('retrieve comments for token %s, url %s' % (token, url)) - for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).order_by(+Comment.published): + logger.info("retrieve comments for token %s, url %s" % (token, url)) + for comment in ( + Comment.select(Comment) + .join(Site) + .where( + (Comment.url == url) + & (Comment.published.is_null(False)) + & (Site.token == token) + ) + .order_by(+Comment.published) + ): d = {} - d['author'] = comment.author_name - d['content'] = comment.content + d["author"] = comment.author_name + d["content"] = comment.content if comment.author_site: - d['site'] = comment.author_site - d['avatar'] = comment.author_gravatar - d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + d["site"] = comment.author_site + d["avatar"] = comment.author_gravatar + d["date"] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) + r = jsonify({"data": comments}) r.status_code = 200 except: - logger.warn('bad request') - r = jsonify({'data': []}) + logger.warn("bad request") + r = jsonify({"data": []}) r.status_code = 400 return r -@app.route("/comments/count", methods=['GET']) +@app.route("/comments/count", methods=["GET"]) def get_comments_count(): try: - token = request.args.get('token', '') - url = request.args.get('url', '') - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() - r = jsonify({'count': count}) + token = request.args.get("token", "") + url = request.args.get("url", "") + count = ( + Comment.select(Comment) + .join(Site) + .where( + (Comment.url == url) + & (Comment.published.is_null(False)) + & (Site.token == token) + ) + .count() + ) + r = jsonify({"count": count}) r.status_code = 200 except: - r = jsonify({'count': 0}) + r = jsonify({"count": 0}) r.status_code = 200 return r - - -@app.route("/comments", methods=['POST']) -def new_comment(): - - try: - data = request.get_json() - logger.info(data) - - # validate token: retrieve site entity - token = data.get('token', '') - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(400) - - # honeypot for spammers - captcha = data.get('captcha', '') - if captcha: - logger.warn('discard spam: data %s' % data) - abort(400) - - processor.enqueue({'request': 'new_comment', 'data': data}) - - except: - logger.exception("new comment failure") - abort(400) - - return "OK" diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 14a32d3..dd233b7 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -12,6 +12,7 @@ from helper.hashing import md5 logger = logging.getLogger(__name__) app = config.flaskapp() + @app.route("/newcomment", methods=["POST"]) def new_form_comment(): @@ -19,11 +20,6 @@ def new_form_comment(): data = request.form logger.info("form data " + str(data)) - # add client IP if provided by HTTP proxy - ip = "" - if "X-Forwarded-For" in request.headers: - ip = request.headers["X-Forwarded-For"] - # validate token: retrieve site entity token = data.get("token", "") site = Site.select().where(Site.token == token).get() @@ -32,12 +28,12 @@ def new_form_comment(): abort(400) # honeypot for spammers - captcha = data.get("remarque", "") + captcha = data.get("remarque", "") if captcha: logger.warn("discard spam: data %s" % data) abort(400) - url = data.get("url", "") + url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() author_site = data.get("site", "").lower().strip() @@ -63,7 +59,6 @@ def new_form_comment(): created=created, notified=None, published=None, - ip=ip, ) comment.save() @@ -73,12 +68,13 @@ def new_form_comment(): return redirect("/redirect/", code=302) + def check_form_data(data): - fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + fields = ["url", "message", "site", "remarque", "author", "token", "email"] d = data.to_dict() for field in fields: if field in d: del d[field] - if d: + if d: logger.warn("additional field: data %s" % data) - abort(400) \ No newline at end of file + abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 8cea605..0839e66 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -8,19 +8,27 @@ from peewee import DateTimeField from peewee import ForeignKeyField from model.site import Site from core.database import get_db +from datetime import datetime class Comment(Model): url = CharField() created = DateTimeField() - notified = DateTimeField(null=True,default=None) + notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default='') - author_gravatar = CharField(default='') - ip = CharField(default='') + author_site = CharField(default="") + author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name='site') + site = ForeignKeyField(Site, related_name="site") class Meta: database = get_db() + + def notify_site_admin(self): + self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.save() + + def publish(self): + self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.save() From 63c487e548c852740cfbcda931c6a066198da836 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 28 Mar 2020 17:57:13 +0100 Subject: [PATCH 264/586] clean-up code --- poetry.lock | 125 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + requirements.txt | 85 ++++++++++++++++++++++ stacosys/__init__.py | 1 - stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/core/cron.py | 23 ++---- stacosys/interface/__init__.py | 0 stacosys/interface/api.py | 94 ++++++++++--------------- stacosys/interface/form.py | 18 ++--- stacosys/model/comment.py | 18 +++-- 11 files changed, 274 insertions(+), 91 deletions(-) create mode 100644 requirements.txt delete mode 100644 stacosys/__init__.py delete mode 100644 stacosys/conf/__init__.py delete mode 100644 stacosys/core/__init__.py delete mode 100644 stacosys/interface/__init__.py diff --git a/poetry.lock b/poetry.lock index ff03a8b..0d8f68b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.3" + [[package]] category = "main" description = "In-process task scheduler with Cron-like capabilities" @@ -48,6 +56,26 @@ dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.int docs = ["sphinx", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "19.10b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=18.1.0" +click = ">=6.5" +pathspec = ">=0.6,<1" +regex = "*" +toml = ">=0.9.4" +typed-ast = ">=1.4.0" + +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] category = "main" description = "Python package for providing Mozilla's CA Bundle." @@ -201,6 +229,14 @@ version = "19.2" pyparsing = ">=2.0.2" six = "*" +[[package]] +category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.7.0" + [[package]] category = "main" description = "a little orm" @@ -301,6 +337,14 @@ optional = false python-versions = "*" version = "2019.3" +[[package]] +category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" +optional = false +python-versions = "*" +version = "2020.2.20" + [[package]] category = "main" description = "Python HTTP for Humans." @@ -327,6 +371,22 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" version = "1.13.0" +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.1" + [[package]] category = "main" description = "tzinfo object for the local timezone" @@ -389,10 +449,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "8325c84a729d400cdc782fcc797efd58a5a598eff2ebb9eeb3f0bf436ecf624b" +content-hash = "6270dbd1455ca926e89cdd874748cf8cee18c2ef5a10a05b6dc7f7100e2482d5" python-versions = "^3.7" [metadata.files] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] apscheduler = [ {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, @@ -405,6 +469,10 @@ attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, ] +black = [ + {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] certifi = [ {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, @@ -486,6 +554,10 @@ packaging = [ {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, ] +pathspec = [ + {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, + {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, +] peewee = [ {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, ] @@ -519,6 +591,29 @@ pytz = [ {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, ] +regex = [ + {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, + {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, + {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, + {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, + {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, + {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, + {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, + {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, + {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, +] requests = [ {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, @@ -527,6 +622,34 @@ six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, ] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] tzlocal = [ {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, diff --git a/pyproject.toml b/pyproject.toml index e06fd3d..8f856e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ requests = "^2.22.0" [tool.poetry.dev-dependencies] pytest = "^5.2" +black = {version = "^19.10b0", allow-prereleases = true} [build-system] requires = ["poetry>=0.12"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a049a0c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,85 @@ +apscheduler==3.6.3 \ + --hash=sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526 \ + --hash=sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f +chardet==3.0.4 \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae +click==7.0 \ + --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ + --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 +flask==1.1.1 \ + --hash=sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6 \ + --hash=sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52 +flask-apscheduler==1.11.0 \ + --hash=sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0 +idna==2.8 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 +itsdangerous==1.1.0 \ + --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ + --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 +jinja2==2.10.3 \ + --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \ + --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de +markdown==3.1.1 \ + --hash=sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c \ + --hash=sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a +markupsafe==1.1.1 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b +peewee==3.13.1 \ + --hash=sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde +profig==0.5.1 \ + --hash=sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6 +pyrss2gen==1.1 \ + --hash=sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be +requests==2.22.0 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +tzlocal==2.0.0 \ + --hash=sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048 \ + --hash=sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590 +urllib3==1.25.7 \ + --hash=sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293 \ + --hash=sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745 +werkzeug==0.16.0 \ + --hash=sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4 \ + --hash=sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7 diff --git a/stacosys/__init__.py b/stacosys/__init__.py deleted file mode 100644 index b794fd4..0000000 --- a/stacosys/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.1.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index b9dbdb6..69dc042 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -56,9 +56,8 @@ def submit_new_comment(): mailer.send(site.admin_email, subject, email_body) logger.debug("new comment processed ") - # update comment - comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() + # notify site admin and save notification datetime + comment.notify_site_admin() def reply_comment_email(data): @@ -98,26 +97,14 @@ def reply_comment_email(data): return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO", "SP"): - - # put a log to help fail2ban - if message[:2].upper() == "SP": # SPAM - if comment.ip: - logger.info( - "SPAM comment from %s: %d" % (comment.ip, comment_id) - ) - else: - logger.info("cannot identify SPAM source: %d" % comment_id) - + if message[:2].upper() in ("NO"): logger.info("discard comment: %d" % comment_id) comment.delete_instance() email_body = get_template("drop_comment").render(original=message) mailer.send(from_email, "Re: " + subject, email_body) else: - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.ip = None - comment.save() + # save publishing datetime + comment.publish() logger.info("commit comment: %d" % comment_id) # rebuild RSS diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index c6f17f8..23270f5 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -10,83 +10,67 @@ from conf import config logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/ping", methods=['GET']) + +@app.route("/ping", methods=["GET"]) def ping(): return "OK" -@app.route("/comments", methods=['GET']) +@app.route("/comments", methods=["GET"]) def query_comments(): comments = [] try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") - logger.info('retrieve comments for token %s, url %s' % (token, url)) - for comment in Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).order_by(+Comment.published): + logger.info("retrieve comments for token %s, url %s" % (token, url)) + for comment in ( + Comment.select(Comment) + .join(Site) + .where( + (Comment.url == url) + & (Comment.published.is_null(False)) + & (Site.token == token) + ) + .order_by(+Comment.published) + ): d = {} - d['author'] = comment.author_name - d['content'] = comment.content + d["author"] = comment.author_name + d["content"] = comment.content if comment.author_site: - d['site'] = comment.author_site - d['avatar'] = comment.author_gravatar - d['date'] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + d["site"] = comment.author_site + d["avatar"] = comment.author_gravatar + d["date"] = comment.published.strftime("%Y-%m-%d %H:%M:%S") logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) + r = jsonify({"data": comments}) r.status_code = 200 except: - logger.warn('bad request') - r = jsonify({'data': []}) + logger.warn("bad request") + r = jsonify({"data": []}) r.status_code = 400 return r -@app.route("/comments/count", methods=['GET']) +@app.route("/comments/count", methods=["GET"]) def get_comments_count(): try: - token = request.args.get('token', '') - url = request.args.get('url', '') - count = Comment.select(Comment).join(Site).where( - (Comment.url == url) & - (Comment.published.is_null(False)) & - (Site.token == token)).count() - r = jsonify({'count': count}) + token = request.args.get("token", "") + url = request.args.get("url", "") + count = ( + Comment.select(Comment) + .join(Site) + .where( + (Comment.url == url) + & (Comment.published.is_null(False)) + & (Site.token == token) + ) + .count() + ) + r = jsonify({"count": count}) r.status_code = 200 except: - r = jsonify({'count': 0}) + r = jsonify({"count": 0}) r.status_code = 200 return r - - -@app.route("/comments", methods=['POST']) -def new_comment(): - - try: - data = request.get_json() - logger.info(data) - - # validate token: retrieve site entity - token = data.get('token', '') - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(400) - - # honeypot for spammers - captcha = data.get('captcha', '') - if captcha: - logger.warn('discard spam: data %s' % data) - abort(400) - - processor.enqueue({'request': 'new_comment', 'data': data}) - - except: - logger.exception("new comment failure") - abort(400) - - return "OK" diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 14a32d3..dd233b7 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -12,6 +12,7 @@ from helper.hashing import md5 logger = logging.getLogger(__name__) app = config.flaskapp() + @app.route("/newcomment", methods=["POST"]) def new_form_comment(): @@ -19,11 +20,6 @@ def new_form_comment(): data = request.form logger.info("form data " + str(data)) - # add client IP if provided by HTTP proxy - ip = "" - if "X-Forwarded-For" in request.headers: - ip = request.headers["X-Forwarded-For"] - # validate token: retrieve site entity token = data.get("token", "") site = Site.select().where(Site.token == token).get() @@ -32,12 +28,12 @@ def new_form_comment(): abort(400) # honeypot for spammers - captcha = data.get("remarque", "") + captcha = data.get("remarque", "") if captcha: logger.warn("discard spam: data %s" % data) abort(400) - url = data.get("url", "") + url = data.get("url", "") author_name = data.get("author", "").strip() author_gravatar = data.get("email", "").strip() author_site = data.get("site", "").lower().strip() @@ -63,7 +59,6 @@ def new_form_comment(): created=created, notified=None, published=None, - ip=ip, ) comment.save() @@ -73,12 +68,13 @@ def new_form_comment(): return redirect("/redirect/", code=302) + def check_form_data(data): - fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + fields = ["url", "message", "site", "remarque", "author", "token", "email"] d = data.to_dict() for field in fields: if field in d: del d[field] - if d: + if d: logger.warn("additional field: data %s" % data) - abort(400) \ No newline at end of file + abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 8cea605..0839e66 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -8,19 +8,27 @@ from peewee import DateTimeField from peewee import ForeignKeyField from model.site import Site from core.database import get_db +from datetime import datetime class Comment(Model): url = CharField() created = DateTimeField() - notified = DateTimeField(null=True,default=None) + notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default='') - author_gravatar = CharField(default='') - ip = CharField(default='') + author_site = CharField(default="") + author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name='site') + site = ForeignKeyField(Site, related_name="site") class Meta: database = get_db() + + def notify_site_admin(self): + self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.save() + + def publish(self): + self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.save() From 3281dd1aaeef0d38431f14c4e205f989b17e7bcb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Mar 2020 18:01:52 +0100 Subject: [PATCH 265/586] python3 --- run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.sh b/run.sh index 24b53b5..fb544ec 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python stacosys/run.py "$@" +python3 stacosys/run.py "$@" From 09f6e1a25031146b78844e4e4e5b29ecd4b1860a Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 28 Mar 2020 18:01:52 +0100 Subject: [PATCH 266/586] python3 --- run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.sh b/run.sh index 24b53b5..fb544ec 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python stacosys/run.py "$@" +python3 stacosys/run.py "$@" From 7f5ea9c4d3d8295b73516ebddab0a4f15a6307a4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 29 Mar 2020 15:43:13 +0200 Subject: [PATCH 267/586] manage imap/smtp directly. remove srmail dependency --- config.ini | 27 ++++--- poetry.lock | 18 ++++- pyproject.toml | 1 + stacosys/conf/config.py | 41 +++++----- stacosys/core/cron.py | 94 +++++++++++------------ stacosys/core/database.py | 3 +- stacosys/core/imap.py | 152 +++++++++++++++++++++++++++++++++++++ stacosys/core/mailer.py | 94 ++++++++++++++++------- stacosys/core/rss.py | 24 +++--- stacosys/core/templater.py | 9 ++- stacosys/helper/hashing.py | 16 ---- stacosys/interface/api.py | 49 ++++++------ stacosys/interface/form.py | 47 ++++++------ stacosys/model/comment.py | 10 +-- stacosys/model/email.py | 14 ++++ stacosys/run.py | 45 ++++++----- 16 files changed, 436 insertions(+), 208 deletions(-) create mode 100755 stacosys/core/imap.py delete mode 100644 stacosys/helper/hashing.py create mode 100644 stacosys/model/email.py diff --git a/config.ini b/config.ini index b9be10c..894d434 100755 --- a/config.ini +++ b/config.ini @@ -1,3 +1,4 @@ +; ; Default configuration [main] lang = fr @@ -5,18 +6,24 @@ db_url = sqlite:///db.sqlite newcomment_polling = 60 [http] -root_url = http://localhost:8100 -host = 0.0.0.0 +host = 127.0.0.1 port = 8100 -[security] -salt = BRRJRqXgGpXWrgTidBPcixIThHpDuKc0 -secret = Uqca5Kc8xuU6THz9 - [rss] -proto = http +proto = https file = comments.xml -[mail] -fetch_polling = 30 -mailer_url = http://localhost:8000 +[imap] +polling = 120 +host = mail.gandi.net +ssl = false +port = 993 +login = blog@mydomain.com +password = MYPASSWORD + +[smtp] +host = mail.gandi.net +starttls = true +port = 587 +login = blog@mydomain.com +password = MYPASSWORD diff --git a/poetry.lock b/poetry.lock index 0d8f68b..760ad71 100644 --- a/poetry.lock +++ b/poetry.lock @@ -363,6 +363,17 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +[[package]] +category = "dev" +description = "a python refactoring library..." +name = "rope" +optional = false +python-versions = "*" +version = "0.16.0" + +[package.extras] +dev = ["pytest"] + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -449,7 +460,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "6270dbd1455ca926e89cdd874748cf8cee18c2ef5a10a05b6dc7f7100e2482d5" +content-hash = "d698fc06cf58f4d228449cb76f48e8d739c323abd535427746d70dbbcaa4924c" python-versions = "^3.7" [metadata.files] @@ -618,6 +629,11 @@ requests = [ {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, ] +rope = [ + {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, + {file = "rope-0.16.0-py3-none-any.whl", hash = "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203"}, + {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, +] six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, diff --git a/pyproject.toml b/pyproject.toml index 8f856e6..2df28ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ requests = "^2.22.0" [tool.poetry.dev-dependencies] pytest = "^5.2" black = {version = "^19.10b0", allow-prereleases = true} +rope = "^0.16.0" [build-system] requires = ["poetry>=0.12"] diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a9cd9b3..712a77f 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,23 +4,30 @@ import profig # constants -FLASK_APP = "flask.app" +FLASK_APP = 'flask.app' -DB_URL = "main.db_url" -LANG = "main.lang" +DB_URL = 'main.db_url' +LANG = 'main.lang' +COMMENT_POLLING = 'main.newcomment_polling' -HTTP_HOST = "http.host" -HTTP_PORT = "http.port" +HTTP_HOST = 'http.host' +HTTP_PORT = 'http.port' -SECURITY_SALT = "security.salt" -SECURITY_SECRET = "security.secret" +RSS_PROTO = 'rss.proto' +RSS_FILE = 'rss.file' -RSS_PROTO = "rss.proto" -RSS_FILE = "rss.file" +IMAP_POLLING = 'imap.polling' +IMAP_SSL = 'imap.ssl' +IMAP_HOST = 'imap.host' +IMAP_PORT = 'imap.port' +IMAP_LOGIN = 'imap.login' +IMAP_PASSWORD = 'imap.password' -MAIL_POLLING = "mail.fetch_polling" -COMMENT_POLLING = "main.newcomment_polling" -MAILER_URL = "mail.mailer_url" +SMTP_STARTTLS = 'smtp.starttls' +SMTP_HOST = 'smtp.host' +SMTP_PORT = 'smtp.port' +SMTP_LOGIN = 'smtp.login' +SMTP_PASSWORD = 'smtp.password' # variable @@ -38,16 +45,12 @@ def get(key): return params[key] -def getInt(key): +def get_int(key): return int(params[key]) -def _str2bool(v): - return v.lower() in ("yes", "true", "t", "1") - - -def getBool(key): - return _str2bool(params[key]) +def get_bool(key): + return params[key].lower() in ('yes', 'true', '1') def flaskapp(): diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 69dc042..0ede34e 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -2,21 +2,21 @@ # -*- coding: utf-8 -*- import logging -from datetime import datetime -import time import re -from core import mailer +import time +from datetime import datetime + +from core import mailer, rss from core.templater import get_template -from core import rss -from model.comment import Comment -from model.comment import Site +from model.comment import Comment, Site +from model.email import Email logger = logging.getLogger(__name__) def cron(func): def wrapper(): - logger.debug("execute CRON " + func.__name__) + logger.debug('execute CRON ' + func.__name__) func() return wrapper @@ -26,10 +26,10 @@ def cron(func): def fetch_mail_answers(): for msg in mailer.fetch(): - if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg["subject"], re.DOTALL): - full_msg = mailer.get(msg["id"]) - if full_msg and reply_comment_email(full_msg['email']): - mailer.delete(msg["id"]) + if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): + if full_msg and _reply_comment_email(msg): + mailer.delete(msg.id) + @cron def submit_new_comment(): @@ -37,42 +37,36 @@ def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): comment_list = ( - "author: %s" % comment.author_name, - "site: %s" % comment.author_site, - "date: %s" % comment.created, - "url: %s" % comment.url, - "", - "%s" % comment.content, - "", + 'author: %s' % comment.author_name, + 'site: %s' % comment.author_site, + 'date: %s' % comment.created, + 'url: %s' % comment.url, + '', + '%s' % comment.content, + '', ) - comment_text = "\n".join(comment_list) - email_body = get_template("new_comment").render( + comment_text = '\n'.join(comment_list) + email_body = get_template('new_comment').render( url=comment.url, comment=comment_text ) # send email site = Site.get(Site.id == comment.site) - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - mailer.send(site.admin_email, subject, email_body) - logger.debug("new comment processed ") + subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, site.token) + if mailer.send(site.admin_email, subject, email_body): + logger.debug('new comment processed ') - # notify site admin and save notification datetime - comment.notify_site_admin() + # notify site admin and save notification datetime + comment.notify_site_admin() + else: + logger.warn('rescheduled. send mail failure ' + subject) -def reply_comment_email(data): +def _reply_comment_email(email): - from_email = data["from"] - subject = data["subject"] - message = "" - for part in data["parts"]: - if part["content-type"] == "text/plain": - message = part["content"] - break - - m = re.search(r"\[(\d+)\:(\w+)\]", subject) + m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) if not m: - logger.warn("ignore corrupted email. No token %s" % subject) + logger.warn('ignore corrupted email. No token %s' % email.subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -81,37 +75,39 @@ def reply_comment_email(data): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn("unknown comment %d" % comment_id) + logger.warn('unknown comment %d' % comment_id) return True if comment.published: - logger.warn("ignore already published email. token %d" % comment_id) + logger.warn('ignore already published email. token %d' % comment_id) return if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return - if not message: - logger.warn("ignore empty email") + if not email.content: + logger.warn('ignore empty email') return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO"): - logger.info("discard comment: %d" % comment_id) + if email.content[:2].upper() in ('NO'): + logger.info('discard comment: %d' % comment_id) comment.delete_instance() - email_body = get_template("drop_comment").render(original=message) - mailer.send(from_email, "Re: " + subject, email_body) + new_email_body = get_template('drop_comment').render(original=email.content) + if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): + logger.warn('minor failure. cannot send rejection mail ' + email.subject) else: # save publishing datetime comment.publish() - logger.info("commit comment: %d" % comment_id) + logger.info('commit comment: %d' % comment_id) # rebuild RSS rss.generate_site(token) # send approval confirmation email to admin - email_body = get_template("approve_comment").render(original=message) - mailer.send(from_email, "Re: " + subject, email_body) + new_email_body = get_template('approve_comment').render(original=email.content) + if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): + logger.warn('minor failure. cannot send approval email ' + email.subject) - return True \ No newline at end of file + return True diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 8404ea2..2557e7e 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -1,9 +1,10 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from conf import config from playhouse.db_url import connect +from conf import config + def get_db(): return connect(config.get(config.DB_URL)) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py new file mode 100755 index 0000000..cc88902 --- /dev/null +++ b/stacosys/core/imap.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +import base64 +import datetime +import email +import imaplib +import logging +import re + +filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) + + +class Mailbox(object): + def __init__(self, host, port, ssl, login, password): + self.logger = logging.getLogger(__name__) + self.host = host + self.port = port + self.ssl = ssl + self.login = login + self.password = password + + def __enter__(self): + if self.ssl: + self.imap = imaplib.IMAP4_SSL(self.host, self.port) + else: + self.imap = imaplib.IMAP4(self.host, self.port) + self.imap.login(self.login, self.password) + return self + + def __exit__(self, type, value, traceback): + self.imap.close() + self.imap.logout() + + def get_count(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + return sum(1 for num in data[0].split()) + + def fetch_raw_message(self, num): + self.imap.select('Inbox') + _, data = self.imap.fetch(str(num), '(RFC822)') + email_msg = email.message_from_bytes(data[0][1]) + return email_msg + + def fetch_message(self, num): + raw_msg = self.fetch_raw_message(num) + msg = {} + msg['encoding'] = 'UTF-8' + msg['index'] = num + dt = parse_date(raw_msg['Date']).strftime('%Y-%m-%d %H:%M:%S') + msg['datetime'] = dt + msg['from'] = raw_msg['From'] + msg['to'] = raw_msg['To'] + subject = email_nonascii_to_uft8(raw_msg['Subject']) + msg['subject'] = subject + parts = [] + attachments = [] + for part in raw_msg.walk(): + if part.is_multipart(): + continue + + content_disposition = part.get('Content-Disposition', None) + if content_disposition: + # we have attachment + r = filename_re.findall(content_disposition) + if r: + filename = sorted(r[0])[1] + else: + filename = 'undefined' + content = base64.b64encode(part.get_payload(decode=True)) + content = content.decode() + a = { + 'filename': email_nonascii_to_uft8(filename), + 'content': content, + 'content-type': part.get_content_type(), + } + attachments.append(a) + else: + part_item = {} + content = part.get_payload(decode=True) + content_type = part.get_content_type() + try: + charset = part.get_param('charset', None) + if charset: + content = to_utf8(content, charset) + elif type(content) == bytes: + content = content.decode('utf8') + except: + self.logger.exception() + # RFC 3676: remove automatic word-wrapping + content = content.replace(' \r\n', ' ') + part_item['content'] = content + part_item['content-type'] = content_type + parts.append(part_item) + if parts: + msg['parts'] = parts + if attachments: + msg['attachments'] = attachments + return msg + + def delete_message(self, num): + self.imap.select('Inbox') + self.imap.store(str(num), '+FLAGS', r'\Deleted') + self.imap.expunge() + + def delete_all(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + for num in data[0].split(): + self.imap.store(num, '+FLAGS', r'\Deleted') + self.imap.expunge() + + def print_msgs(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + for num in reversed(data[0].split()): + status, data = self.imap.fetch(num, '(RFC822)') + self.logger.debug('Message %s\n%s\n' % (num, data[0][1])) + + +def parse_date(v): + if v is None: + return datetime.datetime.now() + + tt = email.utils.parsedate_tz(v) + + if tt is None: + return datetime.datetime.now() + + timestamp = email.utils.mktime_tz(tt) + date = datetime.datetime.fromtimestamp(timestamp) + return date + + +def to_utf8(string, charset): + return string.decode(charset).encode('UTF-8').decode('UTF-8') + + +def email_nonascii_to_uft8(string): + + # RFC 1342 is a recommendation that provides a way to represent non ASCII + # characters inside e-mail in a way that won’t confuse e-mail servers + subject = '' + for v, charset in email.header.decode_header(string): + if charset is None: + if type(v) is bytes: + v = v.decode() + subject = subject + v + else: + subject = subject + to_utf8(v, charset) + return subject diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 9cacebc..cf17294 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -1,43 +1,85 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import logging import json +import logging +import smtplib +from email.mime.text import MIMEText + import requests + from conf import config +from core import imap +from model.email import Email logger = logging.getLogger(__name__) +def _open_mailbox(): + return imap.Mailbox( + config.get(config.IMAP_HOST), + config.get_int(config.IMAP_PORT), + config.get_bool(config.IMAP_SSL), + config.get(config.IMAP_LOGIN), + config.get(config.IMAP_PASSWORD), + ) + + +def _to_dto(msg): + content = 'no plain-text part found in email' + for part in msg['parts']: + if part['content-type'] == 'text/plain': + content = part['content'] + break + return Email( + id=msg['index'], + encoding=msg['encoding'], + date=msg['datetime'], + from_addr=msg['from'], + to_addr=msg['to'], + subject=msg['subject'], + content=content, + ) + + def fetch(): - mails = [] - r = requests.get(config.get(config.MAILER_URL) + "/mbox") - if r.status_code == 200: - payload = r.json() - if payload["count"] > 0: - mails = payload["emails"] - return mails - - -def get(id): - payload = None - r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + str(id)) - if r.status_code == 200: - payload = r.json() - return payload + msgs = [] + try: + with _open_mailbox() as mbox: + count = mbox.get_count() + for num in range(count): + msg = _to_dto(mbox.fetch_message(num + 1)) + msgs.append(msg) + except: + logger.exception('fetch mail exception') + return msgs def send(to_email, subject, message): - headers = {"Content-Type": "application/json; charset=utf-8"} - msg = {"to": to_email, "subject": subject, "content": message} - r = requests.post( - config.get(config.MAILER_URL) + "/mbox", data=json.dumps(msg), headers=headers - ) - if r.status_code in (200, 201): - logger.debug("Email for %s posted" % to_email) - else: - logger.warn("Cannot post email for %s" % to_email) + + # Create the container (outer) email message. + msg = MIMEText(message) + msg['Subject'] = subject + msg['To'] = to_email + msg['From'] = config.get(config.SMTP_LOGIN) + + success = True + try: + s = smtplib.SMTP(config.get(config.SMTP_HOST), config.getInt(config.SMTP_PORT)) + if config.get_bool(config.SMTP_STARTTLS): + s.starttls() + s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) + s.send_message(msg) + s.quit() + except: + logger.exception('send mail exception') + success = False + return success def delete(id): - requests.delete(config.get(config.MAILER_URL) + "/mbox/" + str(id)) + try: + with _open_mailbox() as mbox: + mbox.delete_message(id) + except: + logger.exception('delete mail exception') diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 97f4637..eb33272 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -2,15 +2,18 @@ # -*- coding: UTF-8 -*- from datetime import datetime + import markdown import PyRSS2Gen -from model.site import Site -from model.comment import Comment -from core.templater import get_template + from conf import config +from core.templater import get_template +from model.comment import Comment +from model.site import Site def generate_all(): + for site in Site.select(): generate_site(site.token) @@ -18,7 +21,7 @@ def generate_all(): def generate_site(token): site = Site.select().where(Site.token == token).get() - rss_title = get_template("rss_title_message").render(site=site.name) + rss_title = get_template('rss_title_message').render(site=site.name) md = markdown.Markdown() items = [] @@ -29,24 +32,23 @@ def generate_site(token): .order_by(-Comment.published) .limit(10) ): - item_link = "%s://%s%s" % (config.get(config.RSS_PROTO), site.url, row.url) + item_link = '%s://%s%s' % (config.get(config.RSS_PROTO), site.url, row.url) items.append( PyRSS2Gen.RSSItem( - title="%s - %s://%s%s" + title='%s - %s://%s%s' % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), pubDate=row.published, ) ) rss = PyRSS2Gen.RSS2( title=rss_title, - link="%s://%s" % (config.get(config.RSS_PROTO), site.url), - description="Commentaires du site '%s'" % site.name, + link='%s://%s' % (config.get(config.RSS_PROTO), site.url), + description='Commentaires du site "%s"' % site.name, lastBuildDate=datetime.now(), items=items, ) - rss.write_xml(open(config.get(config.RSS_FILE), "w"), encoding="utf-8") - + rss.write_xml(open(config.get(config.RSS_FILE), 'w'), encoding='utf-8') diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 56b5278..40b4afc 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -2,14 +2,15 @@ # -*- coding: utf-8 -*- import os -from jinja2 import Environment -from jinja2 import FileSystemLoader + +from jinja2 import Environment, FileSystemLoader + from conf import config current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) +template_path = os.path.abspath(os.path.join(current_path, '../templates')) env = Environment(loader=FileSystemLoader(template_path)) def get_template(name): - return env.get_template(config.get(config.LANG) + "/" + name + ".tpl") + return env.get_template(config.get(config.LANG) + '/' + name + '.tpl') diff --git a/stacosys/helper/hashing.py b/stacosys/helper/hashing.py deleted file mode 100644 index fd48851..0000000 --- a/stacosys/helper/hashing.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import hashlib -from conf import config - - -def salt(value): - string = "%s%s" % (value, config.get(config.SECURITY_SALT)) - dk = hashlib.sha256(string.encode()) - return dk.hexdigest() - - -def md5(value): - dk = hashlib.md5(value.encode()) - return dk.hexdigest() diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 23270f5..4061278 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -2,29 +2,31 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort -from model.site import Site -from model.comment import Comment + +from flask import abort, jsonify, request + from conf import config +from model.comment import Comment +from model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/ping", methods=["GET"]) +@app.route('/ping', methods=['GET']) def ping(): - return "OK" + return 'OK' -@app.route("/comments", methods=["GET"]) +@app.route('/comments', methods=['GET']) def query_comments(): comments = [] try: - token = request.args.get("token", "") - url = request.args.get("url", "") + token = request.args.get('token', '') + url = request.args.get('url', '') - logger.info("retrieve comments for token %s, url %s" % (token, url)) + logger.info('retrieve comments for url %s' % (url)) for comment in ( Comment.select(Comment) .join(Site) @@ -35,29 +37,30 @@ def query_comments(): ) .order_by(+Comment.published) ): - d = {} - d["author"] = comment.author_name - d["content"] = comment.content + d = { + 'author': comment.author_name, + 'content': comment.content, + 'avatar': comment.author_gravatar, + 'date': comment.published.strftime('%Y-%m-%d %H:%M:%S') + } if comment.author_site: - d["site"] = comment.author_site - d["avatar"] = comment.author_gravatar - d["date"] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + d['site'] = comment.author_site logger.debug(d) comments.append(d) - r = jsonify({"data": comments}) + r = jsonify({'data': comments}) r.status_code = 200 except: - logger.warn("bad request") - r = jsonify({"data": []}) + logger.warn('bad request') + r = jsonify({'data': []}) r.status_code = 400 return r -@app.route("/comments/count", methods=["GET"]) +@app.route('/comments/count', methods=['GET']) def get_comments_count(): try: - token = request.args.get("token", "") - url = request.args.get("url", "") + token = request.args.get('token', '') + url = request.args.get('url', '') count = ( Comment.select(Comment) .join(Site) @@ -68,9 +71,9 @@ def get_comments_count(): ) .count() ) - r = jsonify({"count": count}) + r = jsonify({'count': count}) r.status_code = 200 except: - r = jsonify({"count": 0}) + r = jsonify({'count': 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index dd233b7..b0f6bc2 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -3,52 +3,53 @@ import logging from datetime import datetime -from flask import request, abort, redirect -from model.site import Site -from model.comment import Comment + +from flask import abort, redirect, request + from conf import config -from helper.hashing import md5 +from model.comment import Comment +from model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/newcomment", methods=["POST"]) +@app.route('/newcomment', methods=['POST']) def new_form_comment(): try: data = request.form - logger.info("form data " + str(data)) + logger.info('form data ' + str(data)) # validate token: retrieve site entity - token = data.get("token", "") + token = data.get('token', '') site = Site.select().where(Site.token == token).get() if site is None: - logger.warn("Unknown site %s" % token) + logger.warn('Unknown site %s' % token) abort(400) # honeypot for spammers - captcha = data.get("remarque", "") + captcha = data.get('remarque', '') if captcha: - logger.warn("discard spam: data %s" % data) + logger.warn('discard spam: data %s' % data) abort(400) - url = data.get("url", "") - author_name = data.get("author", "").strip() - author_gravatar = data.get("email", "").strip() - author_site = data.get("site", "").lower().strip() - if author_site and author_site[:4] != "http": - author_site = "http://" + author_site - message = data.get("message", "") + url = data.get('url', '') + author_name = data.get('author', '').strip() + author_gravatar = data.get('email', '').strip() + author_site = data.get('site', '').lower().strip() + if author_site and author_site[:4] != 'http': + author_site = 'http://' + author_site + message = data.get('message', '') # anti-spam again if not url or not author_name or not message: - logger.warn("empty field: data %s" % data) + logger.warn('empty field: data %s' % data) abort(400) check_form_data(data) # add a row to Comment table - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + created = datetime.now().strftime('%Y-%m-%d %H:%M:%S') comment = Comment( site=site, url=url, @@ -63,18 +64,18 @@ def new_form_comment(): comment.save() except: - logger.exception("new comment failure") + logger.exception('new comment failure') abort(400) - return redirect("/redirect/", code=302) + return redirect('/redirect/', code=302) def check_form_data(data): - fields = ["url", "message", "site", "remarque", "author", "token", "email"] + fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] d = data.to_dict() for field in fields: if field in d: del d[field] if d: - logger.warn("additional field: data %s" % data) + logger.warn('additional field: data %s' % data) abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 0839e66..ab0015f 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -17,18 +17,18 @@ class Comment(Model): notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default="") - author_gravatar = CharField(default="") + author_site = CharField(default='') + author_gravatar = CharField(default='') content = TextField() - site = ForeignKeyField(Site, related_name="site") + site = ForeignKeyField(Site, related_name='site') class Meta: database = get_db() def notify_site_admin(self): - self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.notified = datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.save() def publish(self): - self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.published = datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.save() diff --git a/stacosys/model/email.py b/stacosys/model/email.py new file mode 100644 index 0000000..9557462 --- /dev/null +++ b/stacosys/model/email.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from typing import NamedTuple +from datetime import datetime + +class Email(NamedTuple): + id: int + encoding: str + date: datetime + from_addr: str + to_addr: str + subject: str + content: str diff --git a/stacosys/run.py b/stacosys/run.py index 60e6c30..86fd43e 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -2,12 +2,15 @@ # -*- coding: UTF-8 -*- import argparse -import os import logging +import os + from flask import Flask from flask_apscheduler import APScheduler + from conf import config + # configure logging def configure_logging(level): root_logger = logging.getLogger() @@ -15,7 +18,7 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -26,21 +29,21 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}} + SCHEDULER_EXECUTORS = {'default': {'type': 'threadpool', 'max_workers': 4}} - def __init__(self, mail_polling_seconds, new_comment_polling_seconds): + def __init__(self, imap_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { - "id": "fetch_mail", - "func": "core.cron:fetch_mail_answers", - "trigger": "interval", - "seconds": mail_polling_seconds, + 'id': 'fetch_mail', + 'func': 'core.cron:fetch_mail_answers', + 'trigger': 'interval', + 'seconds': imap_polling_seconds, }, { - "id": "submit_new_comment", - "func": "core.cron:submit_new_comment", - "trigger": "interval", - "seconds": new_comment_polling_seconds, + 'id': 'submit_new_comment', + 'func': 'core.cron:submit_new_comment', + 'trigger': 'interval', + 'seconds': new_comment_polling_seconds, }, ] @@ -53,8 +56,8 @@ def stacosys_server(config_pathname): # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) - logging.getLogger("werkzeug").level = logging.WARNING - logging.getLogger("apscheduler.executors").level = logging.WARNING + logging.getLogger('werkzeug').level = logging.WARNING + logging.getLogger('apscheduler.executors').level = logging.WARNING # initialize database from core import database @@ -64,14 +67,14 @@ def stacosys_server(config_pathname): # cron email fetcher app.config.from_object( JobConfig( - config.getInt(config.MAIL_POLLING), config.getInt(config.COMMENT_POLLING) + config.get_int(config.IMAP_POLLING), config.get_int(config.COMMENT_POLLING) ) ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() - logger.info("Start Stacosys application") + logger.info('Start Stacosys application') # generate RSS for all sites from core import rss @@ -80,10 +83,12 @@ def stacosys_server(config_pathname): # start Flask from interface import api + + logger.info('Load interface %s' % api) + from interface import form - logger.debug("Load interface %s" % api) - logger.debug("Load interface %s" % form) + logger.info('Load interface %s' % form) app.run( host=config.get(config.HTTP_HOST), @@ -93,8 +98,8 @@ def stacosys_server(config_pathname): ) -if __name__ == "__main__": +if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("config", help="config path name") + parser.add_argument('config', help='config path name') args = parser.parse_args() stacosys_server(args.config) From b6da0405fc6202b30b8d3f170918713a8f946c0d Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 29 Mar 2020 15:43:13 +0200 Subject: [PATCH 268/586] manage imap/smtp directly. remove srmail dependency --- config.ini | 27 ++++--- poetry.lock | 18 ++++- pyproject.toml | 1 + stacosys/conf/config.py | 41 +++++----- stacosys/core/cron.py | 94 +++++++++++------------ stacosys/core/database.py | 3 +- stacosys/core/imap.py | 152 +++++++++++++++++++++++++++++++++++++ stacosys/core/mailer.py | 94 ++++++++++++++++------- stacosys/core/rss.py | 24 +++--- stacosys/core/templater.py | 9 ++- stacosys/helper/hashing.py | 16 ---- stacosys/interface/api.py | 49 ++++++------ stacosys/interface/form.py | 47 ++++++------ stacosys/model/comment.py | 10 +-- stacosys/model/email.py | 14 ++++ stacosys/run.py | 45 ++++++----- 16 files changed, 436 insertions(+), 208 deletions(-) create mode 100755 stacosys/core/imap.py delete mode 100644 stacosys/helper/hashing.py create mode 100644 stacosys/model/email.py diff --git a/config.ini b/config.ini index b9be10c..894d434 100755 --- a/config.ini +++ b/config.ini @@ -1,3 +1,4 @@ +; ; Default configuration [main] lang = fr @@ -5,18 +6,24 @@ db_url = sqlite:///db.sqlite newcomment_polling = 60 [http] -root_url = http://localhost:8100 -host = 0.0.0.0 +host = 127.0.0.1 port = 8100 -[security] -salt = BRRJRqXgGpXWrgTidBPcixIThHpDuKc0 -secret = Uqca5Kc8xuU6THz9 - [rss] -proto = http +proto = https file = comments.xml -[mail] -fetch_polling = 30 -mailer_url = http://localhost:8000 +[imap] +polling = 120 +host = mail.gandi.net +ssl = false +port = 993 +login = blog@mydomain.com +password = MYPASSWORD + +[smtp] +host = mail.gandi.net +starttls = true +port = 587 +login = blog@mydomain.com +password = MYPASSWORD diff --git a/poetry.lock b/poetry.lock index 0d8f68b..760ad71 100644 --- a/poetry.lock +++ b/poetry.lock @@ -363,6 +363,17 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +[[package]] +category = "dev" +description = "a python refactoring library..." +name = "rope" +optional = false +python-versions = "*" +version = "0.16.0" + +[package.extras] +dev = ["pytest"] + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -449,7 +460,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "6270dbd1455ca926e89cdd874748cf8cee18c2ef5a10a05b6dc7f7100e2482d5" +content-hash = "d698fc06cf58f4d228449cb76f48e8d739c323abd535427746d70dbbcaa4924c" python-versions = "^3.7" [metadata.files] @@ -618,6 +629,11 @@ requests = [ {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, ] +rope = [ + {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, + {file = "rope-0.16.0-py3-none-any.whl", hash = "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203"}, + {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, +] six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, diff --git a/pyproject.toml b/pyproject.toml index 8f856e6..2df28ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ requests = "^2.22.0" [tool.poetry.dev-dependencies] pytest = "^5.2" black = {version = "^19.10b0", allow-prereleases = true} +rope = "^0.16.0" [build-system] requires = ["poetry>=0.12"] diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a9cd9b3..712a77f 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,23 +4,30 @@ import profig # constants -FLASK_APP = "flask.app" +FLASK_APP = 'flask.app' -DB_URL = "main.db_url" -LANG = "main.lang" +DB_URL = 'main.db_url' +LANG = 'main.lang' +COMMENT_POLLING = 'main.newcomment_polling' -HTTP_HOST = "http.host" -HTTP_PORT = "http.port" +HTTP_HOST = 'http.host' +HTTP_PORT = 'http.port' -SECURITY_SALT = "security.salt" -SECURITY_SECRET = "security.secret" +RSS_PROTO = 'rss.proto' +RSS_FILE = 'rss.file' -RSS_PROTO = "rss.proto" -RSS_FILE = "rss.file" +IMAP_POLLING = 'imap.polling' +IMAP_SSL = 'imap.ssl' +IMAP_HOST = 'imap.host' +IMAP_PORT = 'imap.port' +IMAP_LOGIN = 'imap.login' +IMAP_PASSWORD = 'imap.password' -MAIL_POLLING = "mail.fetch_polling" -COMMENT_POLLING = "main.newcomment_polling" -MAILER_URL = "mail.mailer_url" +SMTP_STARTTLS = 'smtp.starttls' +SMTP_HOST = 'smtp.host' +SMTP_PORT = 'smtp.port' +SMTP_LOGIN = 'smtp.login' +SMTP_PASSWORD = 'smtp.password' # variable @@ -38,16 +45,12 @@ def get(key): return params[key] -def getInt(key): +def get_int(key): return int(params[key]) -def _str2bool(v): - return v.lower() in ("yes", "true", "t", "1") - - -def getBool(key): - return _str2bool(params[key]) +def get_bool(key): + return params[key].lower() in ('yes', 'true', '1') def flaskapp(): diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 69dc042..0ede34e 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -2,21 +2,21 @@ # -*- coding: utf-8 -*- import logging -from datetime import datetime -import time import re -from core import mailer +import time +from datetime import datetime + +from core import mailer, rss from core.templater import get_template -from core import rss -from model.comment import Comment -from model.comment import Site +from model.comment import Comment, Site +from model.email import Email logger = logging.getLogger(__name__) def cron(func): def wrapper(): - logger.debug("execute CRON " + func.__name__) + logger.debug('execute CRON ' + func.__name__) func() return wrapper @@ -26,10 +26,10 @@ def cron(func): def fetch_mail_answers(): for msg in mailer.fetch(): - if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg["subject"], re.DOTALL): - full_msg = mailer.get(msg["id"]) - if full_msg and reply_comment_email(full_msg['email']): - mailer.delete(msg["id"]) + if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): + if full_msg and _reply_comment_email(msg): + mailer.delete(msg.id) + @cron def submit_new_comment(): @@ -37,42 +37,36 @@ def submit_new_comment(): for comment in Comment.select().where(Comment.notified.is_null()): comment_list = ( - "author: %s" % comment.author_name, - "site: %s" % comment.author_site, - "date: %s" % comment.created, - "url: %s" % comment.url, - "", - "%s" % comment.content, - "", + 'author: %s' % comment.author_name, + 'site: %s' % comment.author_site, + 'date: %s' % comment.created, + 'url: %s' % comment.url, + '', + '%s' % comment.content, + '', ) - comment_text = "\n".join(comment_list) - email_body = get_template("new_comment").render( + comment_text = '\n'.join(comment_list) + email_body = get_template('new_comment').render( url=comment.url, comment=comment_text ) # send email site = Site.get(Site.id == comment.site) - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - mailer.send(site.admin_email, subject, email_body) - logger.debug("new comment processed ") + subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, site.token) + if mailer.send(site.admin_email, subject, email_body): + logger.debug('new comment processed ') - # notify site admin and save notification datetime - comment.notify_site_admin() + # notify site admin and save notification datetime + comment.notify_site_admin() + else: + logger.warn('rescheduled. send mail failure ' + subject) -def reply_comment_email(data): +def _reply_comment_email(email): - from_email = data["from"] - subject = data["subject"] - message = "" - for part in data["parts"]: - if part["content-type"] == "text/plain": - message = part["content"] - break - - m = re.search(r"\[(\d+)\:(\w+)\]", subject) + m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) if not m: - logger.warn("ignore corrupted email. No token %s" % subject) + logger.warn('ignore corrupted email. No token %s' % email.subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -81,37 +75,39 @@ def reply_comment_email(data): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn("unknown comment %d" % comment_id) + logger.warn('unknown comment %d' % comment_id) return True if comment.published: - logger.warn("ignore already published email. token %d" % comment_id) + logger.warn('ignore already published email. token %d' % comment_id) return if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return - if not message: - logger.warn("ignore empty email") + if not email.content: + logger.warn('ignore empty email') return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ("NO"): - logger.info("discard comment: %d" % comment_id) + if email.content[:2].upper() in ('NO'): + logger.info('discard comment: %d' % comment_id) comment.delete_instance() - email_body = get_template("drop_comment").render(original=message) - mailer.send(from_email, "Re: " + subject, email_body) + new_email_body = get_template('drop_comment').render(original=email.content) + if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): + logger.warn('minor failure. cannot send rejection mail ' + email.subject) else: # save publishing datetime comment.publish() - logger.info("commit comment: %d" % comment_id) + logger.info('commit comment: %d' % comment_id) # rebuild RSS rss.generate_site(token) # send approval confirmation email to admin - email_body = get_template("approve_comment").render(original=message) - mailer.send(from_email, "Re: " + subject, email_body) + new_email_body = get_template('approve_comment').render(original=email.content) + if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): + logger.warn('minor failure. cannot send approval email ' + email.subject) - return True \ No newline at end of file + return True diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 8404ea2..2557e7e 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -1,9 +1,10 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from conf import config from playhouse.db_url import connect +from conf import config + def get_db(): return connect(config.get(config.DB_URL)) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py new file mode 100755 index 0000000..cc88902 --- /dev/null +++ b/stacosys/core/imap.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +import base64 +import datetime +import email +import imaplib +import logging +import re + +filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) + + +class Mailbox(object): + def __init__(self, host, port, ssl, login, password): + self.logger = logging.getLogger(__name__) + self.host = host + self.port = port + self.ssl = ssl + self.login = login + self.password = password + + def __enter__(self): + if self.ssl: + self.imap = imaplib.IMAP4_SSL(self.host, self.port) + else: + self.imap = imaplib.IMAP4(self.host, self.port) + self.imap.login(self.login, self.password) + return self + + def __exit__(self, type, value, traceback): + self.imap.close() + self.imap.logout() + + def get_count(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + return sum(1 for num in data[0].split()) + + def fetch_raw_message(self, num): + self.imap.select('Inbox') + _, data = self.imap.fetch(str(num), '(RFC822)') + email_msg = email.message_from_bytes(data[0][1]) + return email_msg + + def fetch_message(self, num): + raw_msg = self.fetch_raw_message(num) + msg = {} + msg['encoding'] = 'UTF-8' + msg['index'] = num + dt = parse_date(raw_msg['Date']).strftime('%Y-%m-%d %H:%M:%S') + msg['datetime'] = dt + msg['from'] = raw_msg['From'] + msg['to'] = raw_msg['To'] + subject = email_nonascii_to_uft8(raw_msg['Subject']) + msg['subject'] = subject + parts = [] + attachments = [] + for part in raw_msg.walk(): + if part.is_multipart(): + continue + + content_disposition = part.get('Content-Disposition', None) + if content_disposition: + # we have attachment + r = filename_re.findall(content_disposition) + if r: + filename = sorted(r[0])[1] + else: + filename = 'undefined' + content = base64.b64encode(part.get_payload(decode=True)) + content = content.decode() + a = { + 'filename': email_nonascii_to_uft8(filename), + 'content': content, + 'content-type': part.get_content_type(), + } + attachments.append(a) + else: + part_item = {} + content = part.get_payload(decode=True) + content_type = part.get_content_type() + try: + charset = part.get_param('charset', None) + if charset: + content = to_utf8(content, charset) + elif type(content) == bytes: + content = content.decode('utf8') + except: + self.logger.exception() + # RFC 3676: remove automatic word-wrapping + content = content.replace(' \r\n', ' ') + part_item['content'] = content + part_item['content-type'] = content_type + parts.append(part_item) + if parts: + msg['parts'] = parts + if attachments: + msg['attachments'] = attachments + return msg + + def delete_message(self, num): + self.imap.select('Inbox') + self.imap.store(str(num), '+FLAGS', r'\Deleted') + self.imap.expunge() + + def delete_all(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + for num in data[0].split(): + self.imap.store(num, '+FLAGS', r'\Deleted') + self.imap.expunge() + + def print_msgs(self): + self.imap.select('Inbox') + _, data = self.imap.search(None, 'ALL') + for num in reversed(data[0].split()): + status, data = self.imap.fetch(num, '(RFC822)') + self.logger.debug('Message %s\n%s\n' % (num, data[0][1])) + + +def parse_date(v): + if v is None: + return datetime.datetime.now() + + tt = email.utils.parsedate_tz(v) + + if tt is None: + return datetime.datetime.now() + + timestamp = email.utils.mktime_tz(tt) + date = datetime.datetime.fromtimestamp(timestamp) + return date + + +def to_utf8(string, charset): + return string.decode(charset).encode('UTF-8').decode('UTF-8') + + +def email_nonascii_to_uft8(string): + + # RFC 1342 is a recommendation that provides a way to represent non ASCII + # characters inside e-mail in a way that won’t confuse e-mail servers + subject = '' + for v, charset in email.header.decode_header(string): + if charset is None: + if type(v) is bytes: + v = v.decode() + subject = subject + v + else: + subject = subject + to_utf8(v, charset) + return subject diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 9cacebc..cf17294 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -1,43 +1,85 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import logging import json +import logging +import smtplib +from email.mime.text import MIMEText + import requests + from conf import config +from core import imap +from model.email import Email logger = logging.getLogger(__name__) +def _open_mailbox(): + return imap.Mailbox( + config.get(config.IMAP_HOST), + config.get_int(config.IMAP_PORT), + config.get_bool(config.IMAP_SSL), + config.get(config.IMAP_LOGIN), + config.get(config.IMAP_PASSWORD), + ) + + +def _to_dto(msg): + content = 'no plain-text part found in email' + for part in msg['parts']: + if part['content-type'] == 'text/plain': + content = part['content'] + break + return Email( + id=msg['index'], + encoding=msg['encoding'], + date=msg['datetime'], + from_addr=msg['from'], + to_addr=msg['to'], + subject=msg['subject'], + content=content, + ) + + def fetch(): - mails = [] - r = requests.get(config.get(config.MAILER_URL) + "/mbox") - if r.status_code == 200: - payload = r.json() - if payload["count"] > 0: - mails = payload["emails"] - return mails - - -def get(id): - payload = None - r = requests.get(config.get(config.MAILER_URL) + "/mbox/" + str(id)) - if r.status_code == 200: - payload = r.json() - return payload + msgs = [] + try: + with _open_mailbox() as mbox: + count = mbox.get_count() + for num in range(count): + msg = _to_dto(mbox.fetch_message(num + 1)) + msgs.append(msg) + except: + logger.exception('fetch mail exception') + return msgs def send(to_email, subject, message): - headers = {"Content-Type": "application/json; charset=utf-8"} - msg = {"to": to_email, "subject": subject, "content": message} - r = requests.post( - config.get(config.MAILER_URL) + "/mbox", data=json.dumps(msg), headers=headers - ) - if r.status_code in (200, 201): - logger.debug("Email for %s posted" % to_email) - else: - logger.warn("Cannot post email for %s" % to_email) + + # Create the container (outer) email message. + msg = MIMEText(message) + msg['Subject'] = subject + msg['To'] = to_email + msg['From'] = config.get(config.SMTP_LOGIN) + + success = True + try: + s = smtplib.SMTP(config.get(config.SMTP_HOST), config.getInt(config.SMTP_PORT)) + if config.get_bool(config.SMTP_STARTTLS): + s.starttls() + s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) + s.send_message(msg) + s.quit() + except: + logger.exception('send mail exception') + success = False + return success def delete(id): - requests.delete(config.get(config.MAILER_URL) + "/mbox/" + str(id)) + try: + with _open_mailbox() as mbox: + mbox.delete_message(id) + except: + logger.exception('delete mail exception') diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 97f4637..eb33272 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -2,15 +2,18 @@ # -*- coding: UTF-8 -*- from datetime import datetime + import markdown import PyRSS2Gen -from model.site import Site -from model.comment import Comment -from core.templater import get_template + from conf import config +from core.templater import get_template +from model.comment import Comment +from model.site import Site def generate_all(): + for site in Site.select(): generate_site(site.token) @@ -18,7 +21,7 @@ def generate_all(): def generate_site(token): site = Site.select().where(Site.token == token).get() - rss_title = get_template("rss_title_message").render(site=site.name) + rss_title = get_template('rss_title_message').render(site=site.name) md = markdown.Markdown() items = [] @@ -29,24 +32,23 @@ def generate_site(token): .order_by(-Comment.published) .limit(10) ): - item_link = "%s://%s%s" % (config.get(config.RSS_PROTO), site.url, row.url) + item_link = '%s://%s%s' % (config.get(config.RSS_PROTO), site.url, row.url) items.append( PyRSS2Gen.RSSItem( - title="%s - %s://%s%s" + title='%s - %s://%s%s' % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), link=item_link, description=md.convert(row.content), - guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), pubDate=row.published, ) ) rss = PyRSS2Gen.RSS2( title=rss_title, - link="%s://%s" % (config.get(config.RSS_PROTO), site.url), - description="Commentaires du site '%s'" % site.name, + link='%s://%s' % (config.get(config.RSS_PROTO), site.url), + description='Commentaires du site "%s"' % site.name, lastBuildDate=datetime.now(), items=items, ) - rss.write_xml(open(config.get(config.RSS_FILE), "w"), encoding="utf-8") - + rss.write_xml(open(config.get(config.RSS_FILE), 'w'), encoding='utf-8') diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 56b5278..40b4afc 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -2,14 +2,15 @@ # -*- coding: utf-8 -*- import os -from jinja2 import Environment -from jinja2 import FileSystemLoader + +from jinja2 import Environment, FileSystemLoader + from conf import config current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) +template_path = os.path.abspath(os.path.join(current_path, '../templates')) env = Environment(loader=FileSystemLoader(template_path)) def get_template(name): - return env.get_template(config.get(config.LANG) + "/" + name + ".tpl") + return env.get_template(config.get(config.LANG) + '/' + name + '.tpl') diff --git a/stacosys/helper/hashing.py b/stacosys/helper/hashing.py deleted file mode 100644 index fd48851..0000000 --- a/stacosys/helper/hashing.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import hashlib -from conf import config - - -def salt(value): - string = "%s%s" % (value, config.get(config.SECURITY_SALT)) - dk = hashlib.sha256(string.encode()) - return dk.hexdigest() - - -def md5(value): - dk = hashlib.md5(value.encode()) - return dk.hexdigest() diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 23270f5..4061278 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -2,29 +2,31 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort -from model.site import Site -from model.comment import Comment + +from flask import abort, jsonify, request + from conf import config +from model.comment import Comment +from model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/ping", methods=["GET"]) +@app.route('/ping', methods=['GET']) def ping(): - return "OK" + return 'OK' -@app.route("/comments", methods=["GET"]) +@app.route('/comments', methods=['GET']) def query_comments(): comments = [] try: - token = request.args.get("token", "") - url = request.args.get("url", "") + token = request.args.get('token', '') + url = request.args.get('url', '') - logger.info("retrieve comments for token %s, url %s" % (token, url)) + logger.info('retrieve comments for url %s' % (url)) for comment in ( Comment.select(Comment) .join(Site) @@ -35,29 +37,30 @@ def query_comments(): ) .order_by(+Comment.published) ): - d = {} - d["author"] = comment.author_name - d["content"] = comment.content + d = { + 'author': comment.author_name, + 'content': comment.content, + 'avatar': comment.author_gravatar, + 'date': comment.published.strftime('%Y-%m-%d %H:%M:%S') + } if comment.author_site: - d["site"] = comment.author_site - d["avatar"] = comment.author_gravatar - d["date"] = comment.published.strftime("%Y-%m-%d %H:%M:%S") + d['site'] = comment.author_site logger.debug(d) comments.append(d) - r = jsonify({"data": comments}) + r = jsonify({'data': comments}) r.status_code = 200 except: - logger.warn("bad request") - r = jsonify({"data": []}) + logger.warn('bad request') + r = jsonify({'data': []}) r.status_code = 400 return r -@app.route("/comments/count", methods=["GET"]) +@app.route('/comments/count', methods=['GET']) def get_comments_count(): try: - token = request.args.get("token", "") - url = request.args.get("url", "") + token = request.args.get('token', '') + url = request.args.get('url', '') count = ( Comment.select(Comment) .join(Site) @@ -68,9 +71,9 @@ def get_comments_count(): ) .count() ) - r = jsonify({"count": count}) + r = jsonify({'count': count}) r.status_code = 200 except: - r = jsonify({"count": 0}) + r = jsonify({'count': 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index dd233b7..b0f6bc2 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -3,52 +3,53 @@ import logging from datetime import datetime -from flask import request, abort, redirect -from model.site import Site -from model.comment import Comment + +from flask import abort, redirect, request + from conf import config -from helper.hashing import md5 +from model.comment import Comment +from model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() -@app.route("/newcomment", methods=["POST"]) +@app.route('/newcomment', methods=['POST']) def new_form_comment(): try: data = request.form - logger.info("form data " + str(data)) + logger.info('form data ' + str(data)) # validate token: retrieve site entity - token = data.get("token", "") + token = data.get('token', '') site = Site.select().where(Site.token == token).get() if site is None: - logger.warn("Unknown site %s" % token) + logger.warn('Unknown site %s' % token) abort(400) # honeypot for spammers - captcha = data.get("remarque", "") + captcha = data.get('remarque', '') if captcha: - logger.warn("discard spam: data %s" % data) + logger.warn('discard spam: data %s' % data) abort(400) - url = data.get("url", "") - author_name = data.get("author", "").strip() - author_gravatar = data.get("email", "").strip() - author_site = data.get("site", "").lower().strip() - if author_site and author_site[:4] != "http": - author_site = "http://" + author_site - message = data.get("message", "") + url = data.get('url', '') + author_name = data.get('author', '').strip() + author_gravatar = data.get('email', '').strip() + author_site = data.get('site', '').lower().strip() + if author_site and author_site[:4] != 'http': + author_site = 'http://' + author_site + message = data.get('message', '') # anti-spam again if not url or not author_name or not message: - logger.warn("empty field: data %s" % data) + logger.warn('empty field: data %s' % data) abort(400) check_form_data(data) # add a row to Comment table - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + created = datetime.now().strftime('%Y-%m-%d %H:%M:%S') comment = Comment( site=site, url=url, @@ -63,18 +64,18 @@ def new_form_comment(): comment.save() except: - logger.exception("new comment failure") + logger.exception('new comment failure') abort(400) - return redirect("/redirect/", code=302) + return redirect('/redirect/', code=302) def check_form_data(data): - fields = ["url", "message", "site", "remarque", "author", "token", "email"] + fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] d = data.to_dict() for field in fields: if field in d: del d[field] if d: - logger.warn("additional field: data %s" % data) + logger.warn('additional field: data %s' % data) abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 0839e66..ab0015f 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -17,18 +17,18 @@ class Comment(Model): notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default="") - author_gravatar = CharField(default="") + author_site = CharField(default='') + author_gravatar = CharField(default='') content = TextField() - site = ForeignKeyField(Site, related_name="site") + site = ForeignKeyField(Site, related_name='site') class Meta: database = get_db() def notify_site_admin(self): - self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.notified = datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.save() def publish(self): - self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.published = datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.save() diff --git a/stacosys/model/email.py b/stacosys/model/email.py new file mode 100644 index 0000000..9557462 --- /dev/null +++ b/stacosys/model/email.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from typing import NamedTuple +from datetime import datetime + +class Email(NamedTuple): + id: int + encoding: str + date: datetime + from_addr: str + to_addr: str + subject: str + content: str diff --git a/stacosys/run.py b/stacosys/run.py index 60e6c30..86fd43e 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -2,12 +2,15 @@ # -*- coding: UTF-8 -*- import argparse -import os import logging +import os + from flask import Flask from flask_apscheduler import APScheduler + from conf import config + # configure logging def configure_logging(level): root_logger = logging.getLogger() @@ -15,7 +18,7 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -26,21 +29,21 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}} + SCHEDULER_EXECUTORS = {'default': {'type': 'threadpool', 'max_workers': 4}} - def __init__(self, mail_polling_seconds, new_comment_polling_seconds): + def __init__(self, imap_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { - "id": "fetch_mail", - "func": "core.cron:fetch_mail_answers", - "trigger": "interval", - "seconds": mail_polling_seconds, + 'id': 'fetch_mail', + 'func': 'core.cron:fetch_mail_answers', + 'trigger': 'interval', + 'seconds': imap_polling_seconds, }, { - "id": "submit_new_comment", - "func": "core.cron:submit_new_comment", - "trigger": "interval", - "seconds": new_comment_polling_seconds, + 'id': 'submit_new_comment', + 'func': 'core.cron:submit_new_comment', + 'trigger': 'interval', + 'seconds': new_comment_polling_seconds, }, ] @@ -53,8 +56,8 @@ def stacosys_server(config_pathname): # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) - logging.getLogger("werkzeug").level = logging.WARNING - logging.getLogger("apscheduler.executors").level = logging.WARNING + logging.getLogger('werkzeug').level = logging.WARNING + logging.getLogger('apscheduler.executors').level = logging.WARNING # initialize database from core import database @@ -64,14 +67,14 @@ def stacosys_server(config_pathname): # cron email fetcher app.config.from_object( JobConfig( - config.getInt(config.MAIL_POLLING), config.getInt(config.COMMENT_POLLING) + config.get_int(config.IMAP_POLLING), config.get_int(config.COMMENT_POLLING) ) ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() - logger.info("Start Stacosys application") + logger.info('Start Stacosys application') # generate RSS for all sites from core import rss @@ -80,10 +83,12 @@ def stacosys_server(config_pathname): # start Flask from interface import api + + logger.info('Load interface %s' % api) + from interface import form - logger.debug("Load interface %s" % api) - logger.debug("Load interface %s" % form) + logger.info('Load interface %s' % form) app.run( host=config.get(config.HTTP_HOST), @@ -93,8 +98,8 @@ def stacosys_server(config_pathname): ) -if __name__ == "__main__": +if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("config", help="config path name") + parser.add_argument('config', help='config path name') args = parser.parse_args() stacosys_server(args.config) From f8636feaab24a942292c391b083481ba34268875 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 29 Mar 2020 15:57:25 +0200 Subject: [PATCH 269/586] fix fn name --- stacosys/core/mailer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index cf17294..2211e6f 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -65,7 +65,7 @@ def send(to_email, subject, message): success = True try: - s = smtplib.SMTP(config.get(config.SMTP_HOST), config.getInt(config.SMTP_PORT)) + s = smtplib.SMTP(config.get(config.SMTP_HOST), config.get_int(config.SMTP_PORT)) if config.get_bool(config.SMTP_STARTTLS): s.starttls() s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) From 753196962743760b3a39747138f0eb7f0aad4372 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 29 Mar 2020 15:57:25 +0200 Subject: [PATCH 270/586] fix fn name --- stacosys/core/mailer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index cf17294..2211e6f 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -65,7 +65,7 @@ def send(to_email, subject, message): success = True try: - s = smtplib.SMTP(config.get(config.SMTP_HOST), config.getInt(config.SMTP_PORT)) + s = smtplib.SMTP(config.get(config.SMTP_HOST), config.get_int(config.SMTP_PORT)) if config.get_bool(config.SMTP_STARTTLS): s.starttls() s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) From 243fe6903d3d2ba12acc36c6b3ccdec46ead4517 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 29 Mar 2020 16:05:12 +0200 Subject: [PATCH 271/586] fix var --- stacosys/core/cron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 0ede34e..31fd697 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -27,7 +27,7 @@ def fetch_mail_answers(): for msg in mailer.fetch(): if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): - if full_msg and _reply_comment_email(msg): + if _reply_comment_email(msg): mailer.delete(msg.id) From f1c5d834950b774d4600705f9af3e0302401e513 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 29 Mar 2020 16:05:12 +0200 Subject: [PATCH 272/586] fix var --- stacosys/core/cron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 0ede34e..31fd697 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -27,7 +27,7 @@ def fetch_mail_answers(): for msg in mailer.fetch(): if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): - if full_msg and _reply_comment_email(msg): + if _reply_comment_email(msg): mailer.delete(msg.id) From 44ea468b712828281f57e01fce256f26517fddfc Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 31 Mar 2020 19:53:59 +0200 Subject: [PATCH 273/586] add type checking --- stacosys/core/imap.py | 91 ++++++++++++++++++++++------------------- stacosys/core/mailer.py | 32 ++++----------- stacosys/model/email.py | 17 +++++++- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index cc88902..a5d5827 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -8,6 +8,8 @@ import imaplib import logging import re +from model.email import Attachment, Email, Part + filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) @@ -33,90 +35,93 @@ class Mailbox(object): self.imap.logout() def get_count(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") return sum(1 for num in data[0].split()) def fetch_raw_message(self, num): - self.imap.select('Inbox') - _, data = self.imap.fetch(str(num), '(RFC822)') + self.imap.select("Inbox") + _, data = self.imap.fetch(str(num), "(RFC822)") email_msg = email.message_from_bytes(data[0][1]) return email_msg def fetch_message(self, num): raw_msg = self.fetch_raw_message(num) - msg = {} - msg['encoding'] = 'UTF-8' - msg['index'] = num - dt = parse_date(raw_msg['Date']).strftime('%Y-%m-%d %H:%M:%S') - msg['datetime'] = dt - msg['from'] = raw_msg['From'] - msg['to'] = raw_msg['To'] - subject = email_nonascii_to_uft8(raw_msg['Subject']) - msg['subject'] = subject + + msg = Email( + id=num, + encoding="UTF-8", + date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), + from_addr=raw_msg["From"], + to_addr=raw_msg["To"], + subject=email_nonascii_to_uft8(raw_msg["Subject"]), + ) + parts = [] attachments = [] for part in raw_msg.walk(): if part.is_multipart(): continue - content_disposition = part.get('Content-Disposition', None) + content_disposition = part.get("Content-Disposition", None) if content_disposition: # we have attachment r = filename_re.findall(content_disposition) if r: filename = sorted(r[0])[1] else: - filename = 'undefined' + filename = "undefined" content = base64.b64encode(part.get_payload(decode=True)) content = content.decode() - a = { - 'filename': email_nonascii_to_uft8(filename), - 'content': content, - 'content-type': part.get_content_type(), - } - attachments.append(a) + attachments.append( + Attachment( + filename=email_nonascii_to_uft8(filename), + content=content, + content_type=part.get_content_type(), + ) + ) else: part_item = {} content = part.get_payload(decode=True) - content_type = part.get_content_type() try: - charset = part.get_param('charset', None) + charset = part.get_param("charset", None) if charset: content = to_utf8(content, charset) elif type(content) == bytes: - content = content.decode('utf8') + content = content.decode("utf8") except: self.logger.exception() # RFC 3676: remove automatic word-wrapping - content = content.replace(' \r\n', ' ') - part_item['content'] = content - part_item['content-type'] = content_type - parts.append(part_item) - if parts: - msg['parts'] = parts - if attachments: - msg['attachments'] = attachments + content = content.replace(" \r\n", " ") + + parts.append( + Part(content=content, content_type=part.get_content_type()) + ) + + if part.get_content_type() == "text/plain": + msg.plain_text_content = content + msg.parts = parts + msg.attachments = attachments return msg def delete_message(self, num): - self.imap.select('Inbox') - self.imap.store(str(num), '+FLAGS', r'\Deleted') + self.imap.select("Inbox") + self.imap.store(str(num), "+FLAGS", r"\Deleted") self.imap.expunge() def delete_all(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") for num in data[0].split(): - self.imap.store(num, '+FLAGS', r'\Deleted') + self.imap.store(num, "+FLAGS", r"\Deleted") self.imap.expunge() def print_msgs(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") for num in reversed(data[0].split()): - status, data = self.imap.fetch(num, '(RFC822)') - self.logger.debug('Message %s\n%s\n' % (num, data[0][1])) + status, data = self.imap.fetch(num, "(RFC822)") + self.logger.debug("Message %s\n%s\n" % (num, data[0][1])) def parse_date(v): @@ -134,14 +139,14 @@ def parse_date(v): def to_utf8(string, charset): - return string.decode(charset).encode('UTF-8').decode('UTF-8') + return string.decode(charset).encode("UTF-8").decode("UTF-8") def email_nonascii_to_uft8(string): # RFC 1342 is a recommendation that provides a way to represent non ASCII # characters inside e-mail in a way that won’t confuse e-mail servers - subject = '' + subject = "" for v, charset in email.header.decode_header(string): if charset is None: if type(v) is bytes: diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 2211e6f..4697dc1 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -25,33 +25,15 @@ def _open_mailbox(): ) -def _to_dto(msg): - content = 'no plain-text part found in email' - for part in msg['parts']: - if part['content-type'] == 'text/plain': - content = part['content'] - break - return Email( - id=msg['index'], - encoding=msg['encoding'], - date=msg['datetime'], - from_addr=msg['from'], - to_addr=msg['to'], - subject=msg['subject'], - content=content, - ) - - def fetch(): msgs = [] try: with _open_mailbox() as mbox: count = mbox.get_count() for num in range(count): - msg = _to_dto(mbox.fetch_message(num + 1)) - msgs.append(msg) + msgs.append(mbox.fetch_message(num + 1)) except: - logger.exception('fetch mail exception') + logger.exception("fetch mail exception") return msgs @@ -59,9 +41,9 @@ def send(to_email, subject, message): # Create the container (outer) email message. msg = MIMEText(message) - msg['Subject'] = subject - msg['To'] = to_email - msg['From'] = config.get(config.SMTP_LOGIN) + msg["Subject"] = subject + msg["To"] = to_email + msg["From"] = config.get(config.SMTP_LOGIN) success = True try: @@ -72,7 +54,7 @@ def send(to_email, subject, message): s.send_message(msg) s.quit() except: - logger.exception('send mail exception') + logger.exception("send mail exception") success = False return success @@ -82,4 +64,4 @@ def delete(id): with _open_mailbox() as mbox: mbox.delete_message(id) except: - logger.exception('delete mail exception') + logger.exception("delete mail exception") diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 9557462..5f3805b 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -2,8 +2,21 @@ # -*- coding: UTF-8 -*- from typing import NamedTuple +from typing import List from datetime import datetime + +class Part(NamedTuple): + content: str + content_type: str + + +class Attachment(NamedTuple): + filename: str + content: str + content_type: str + + class Email(NamedTuple): id: int encoding: str @@ -11,4 +24,6 @@ class Email(NamedTuple): from_addr: str to_addr: str subject: str - content: str + parts: List[Part] + attachments: List[Attachment] + plain_text_content: str = 'no plain-text part' From e95c6264a0acb34fb486ecba9a33717b34e12974 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 31 Mar 2020 19:53:59 +0200 Subject: [PATCH 274/586] add type checking --- stacosys/core/imap.py | 91 ++++++++++++++++++++++------------------- stacosys/core/mailer.py | 32 ++++----------- stacosys/model/email.py | 17 +++++++- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index cc88902..a5d5827 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -8,6 +8,8 @@ import imaplib import logging import re +from model.email import Attachment, Email, Part + filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) @@ -33,90 +35,93 @@ class Mailbox(object): self.imap.logout() def get_count(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") return sum(1 for num in data[0].split()) def fetch_raw_message(self, num): - self.imap.select('Inbox') - _, data = self.imap.fetch(str(num), '(RFC822)') + self.imap.select("Inbox") + _, data = self.imap.fetch(str(num), "(RFC822)") email_msg = email.message_from_bytes(data[0][1]) return email_msg def fetch_message(self, num): raw_msg = self.fetch_raw_message(num) - msg = {} - msg['encoding'] = 'UTF-8' - msg['index'] = num - dt = parse_date(raw_msg['Date']).strftime('%Y-%m-%d %H:%M:%S') - msg['datetime'] = dt - msg['from'] = raw_msg['From'] - msg['to'] = raw_msg['To'] - subject = email_nonascii_to_uft8(raw_msg['Subject']) - msg['subject'] = subject + + msg = Email( + id=num, + encoding="UTF-8", + date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), + from_addr=raw_msg["From"], + to_addr=raw_msg["To"], + subject=email_nonascii_to_uft8(raw_msg["Subject"]), + ) + parts = [] attachments = [] for part in raw_msg.walk(): if part.is_multipart(): continue - content_disposition = part.get('Content-Disposition', None) + content_disposition = part.get("Content-Disposition", None) if content_disposition: # we have attachment r = filename_re.findall(content_disposition) if r: filename = sorted(r[0])[1] else: - filename = 'undefined' + filename = "undefined" content = base64.b64encode(part.get_payload(decode=True)) content = content.decode() - a = { - 'filename': email_nonascii_to_uft8(filename), - 'content': content, - 'content-type': part.get_content_type(), - } - attachments.append(a) + attachments.append( + Attachment( + filename=email_nonascii_to_uft8(filename), + content=content, + content_type=part.get_content_type(), + ) + ) else: part_item = {} content = part.get_payload(decode=True) - content_type = part.get_content_type() try: - charset = part.get_param('charset', None) + charset = part.get_param("charset", None) if charset: content = to_utf8(content, charset) elif type(content) == bytes: - content = content.decode('utf8') + content = content.decode("utf8") except: self.logger.exception() # RFC 3676: remove automatic word-wrapping - content = content.replace(' \r\n', ' ') - part_item['content'] = content - part_item['content-type'] = content_type - parts.append(part_item) - if parts: - msg['parts'] = parts - if attachments: - msg['attachments'] = attachments + content = content.replace(" \r\n", " ") + + parts.append( + Part(content=content, content_type=part.get_content_type()) + ) + + if part.get_content_type() == "text/plain": + msg.plain_text_content = content + msg.parts = parts + msg.attachments = attachments return msg def delete_message(self, num): - self.imap.select('Inbox') - self.imap.store(str(num), '+FLAGS', r'\Deleted') + self.imap.select("Inbox") + self.imap.store(str(num), "+FLAGS", r"\Deleted") self.imap.expunge() def delete_all(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") for num in data[0].split(): - self.imap.store(num, '+FLAGS', r'\Deleted') + self.imap.store(num, "+FLAGS", r"\Deleted") self.imap.expunge() def print_msgs(self): - self.imap.select('Inbox') - _, data = self.imap.search(None, 'ALL') + self.imap.select("Inbox") + _, data = self.imap.search(None, "ALL") for num in reversed(data[0].split()): - status, data = self.imap.fetch(num, '(RFC822)') - self.logger.debug('Message %s\n%s\n' % (num, data[0][1])) + status, data = self.imap.fetch(num, "(RFC822)") + self.logger.debug("Message %s\n%s\n" % (num, data[0][1])) def parse_date(v): @@ -134,14 +139,14 @@ def parse_date(v): def to_utf8(string, charset): - return string.decode(charset).encode('UTF-8').decode('UTF-8') + return string.decode(charset).encode("UTF-8").decode("UTF-8") def email_nonascii_to_uft8(string): # RFC 1342 is a recommendation that provides a way to represent non ASCII # characters inside e-mail in a way that won’t confuse e-mail servers - subject = '' + subject = "" for v, charset in email.header.decode_header(string): if charset is None: if type(v) is bytes: diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 2211e6f..4697dc1 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -25,33 +25,15 @@ def _open_mailbox(): ) -def _to_dto(msg): - content = 'no plain-text part found in email' - for part in msg['parts']: - if part['content-type'] == 'text/plain': - content = part['content'] - break - return Email( - id=msg['index'], - encoding=msg['encoding'], - date=msg['datetime'], - from_addr=msg['from'], - to_addr=msg['to'], - subject=msg['subject'], - content=content, - ) - - def fetch(): msgs = [] try: with _open_mailbox() as mbox: count = mbox.get_count() for num in range(count): - msg = _to_dto(mbox.fetch_message(num + 1)) - msgs.append(msg) + msgs.append(mbox.fetch_message(num + 1)) except: - logger.exception('fetch mail exception') + logger.exception("fetch mail exception") return msgs @@ -59,9 +41,9 @@ def send(to_email, subject, message): # Create the container (outer) email message. msg = MIMEText(message) - msg['Subject'] = subject - msg['To'] = to_email - msg['From'] = config.get(config.SMTP_LOGIN) + msg["Subject"] = subject + msg["To"] = to_email + msg["From"] = config.get(config.SMTP_LOGIN) success = True try: @@ -72,7 +54,7 @@ def send(to_email, subject, message): s.send_message(msg) s.quit() except: - logger.exception('send mail exception') + logger.exception("send mail exception") success = False return success @@ -82,4 +64,4 @@ def delete(id): with _open_mailbox() as mbox: mbox.delete_message(id) except: - logger.exception('delete mail exception') + logger.exception("delete mail exception") diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 9557462..5f3805b 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -2,8 +2,21 @@ # -*- coding: UTF-8 -*- from typing import NamedTuple +from typing import List from datetime import datetime + +class Part(NamedTuple): + content: str + content_type: str + + +class Attachment(NamedTuple): + filename: str + content: str + content_type: str + + class Email(NamedTuple): id: int encoding: str @@ -11,4 +24,6 @@ class Email(NamedTuple): from_addr: str to_addr: str subject: str - content: str + parts: List[Part] + attachments: List[Attachment] + plain_text_content: str = 'no plain-text part' From 7f3b8adda4e7e49ec72413b3587edd69890a54cd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 31 Mar 2020 20:09:38 +0200 Subject: [PATCH 275/586] fix named tuple --- stacosys/core/imap.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index a5d5827..d0bf85a 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -48,15 +48,6 @@ class Mailbox(object): def fetch_message(self, num): raw_msg = self.fetch_raw_message(num) - msg = Email( - id=num, - encoding="UTF-8", - date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), - from_addr=raw_msg["From"], - to_addr=raw_msg["To"], - subject=email_nonascii_to_uft8(raw_msg["Subject"]), - ) - parts = [] attachments = [] for part in raw_msg.walk(): @@ -100,9 +91,17 @@ class Mailbox(object): if part.get_content_type() == "text/plain": msg.plain_text_content = content - msg.parts = parts - msg.attachments = attachments - return msg + + return Email( + id=num, + encoding="UTF-8", + date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), + from_addr=raw_msg["From"], + to_addr=raw_msg["To"], + subject=email_nonascii_to_uft8(raw_msg["Subject"]), + parts=parts, + attachments=attachments + ) def delete_message(self, num): self.imap.select("Inbox") From ea7585f0789c4c5e5671a165eb71ea4e890e1d69 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 31 Mar 2020 20:09:38 +0200 Subject: [PATCH 276/586] fix named tuple --- stacosys/core/imap.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index a5d5827..d0bf85a 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -48,15 +48,6 @@ class Mailbox(object): def fetch_message(self, num): raw_msg = self.fetch_raw_message(num) - msg = Email( - id=num, - encoding="UTF-8", - date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), - from_addr=raw_msg["From"], - to_addr=raw_msg["To"], - subject=email_nonascii_to_uft8(raw_msg["Subject"]), - ) - parts = [] attachments = [] for part in raw_msg.walk(): @@ -100,9 +91,17 @@ class Mailbox(object): if part.get_content_type() == "text/plain": msg.plain_text_content = content - msg.parts = parts - msg.attachments = attachments - return msg + + return Email( + id=num, + encoding="UTF-8", + date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), + from_addr=raw_msg["From"], + to_addr=raw_msg["To"], + subject=email_nonascii_to_uft8(raw_msg["Subject"]), + parts=parts, + attachments=attachments + ) def delete_message(self, num): self.imap.select("Inbox") From 92a6830364dad18750b676ec67587cb6ff1a4c3c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 31 Mar 2020 20:15:29 +0200 Subject: [PATCH 277/586] fix plain text part content --- stacosys/core/imap.py | 6 ++++-- stacosys/model/email.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index d0bf85a..e662da5 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -50,6 +50,7 @@ class Mailbox(object): parts = [] attachments = [] + plain_text_content: 'no plain-text part' for part in raw_msg.walk(): if part.is_multipart(): continue @@ -90,7 +91,7 @@ class Mailbox(object): ) if part.get_content_type() == "text/plain": - msg.plain_text_content = content + plain_text_content = content return Email( id=num, @@ -100,7 +101,8 @@ class Mailbox(object): to_addr=raw_msg["To"], subject=email_nonascii_to_uft8(raw_msg["Subject"]), parts=parts, - attachments=attachments + attachments=attachments, + plain_text_content = plain_text_content ) def delete_message(self, num): diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 5f3805b..8852506 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -26,4 +26,4 @@ class Email(NamedTuple): subject: str parts: List[Part] attachments: List[Attachment] - plain_text_content: str = 'no plain-text part' + plain_text_content: str From 21ab9c7e8b33a549d094f8d9c3174fa68128ad45 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 31 Mar 2020 20:15:29 +0200 Subject: [PATCH 278/586] fix plain text part content --- stacosys/core/imap.py | 6 ++++-- stacosys/model/email.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index d0bf85a..e662da5 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -50,6 +50,7 @@ class Mailbox(object): parts = [] attachments = [] + plain_text_content: 'no plain-text part' for part in raw_msg.walk(): if part.is_multipart(): continue @@ -90,7 +91,7 @@ class Mailbox(object): ) if part.get_content_type() == "text/plain": - msg.plain_text_content = content + plain_text_content = content return Email( id=num, @@ -100,7 +101,8 @@ class Mailbox(object): to_addr=raw_msg["To"], subject=email_nonascii_to_uft8(raw_msg["Subject"]), parts=parts, - attachments=attachments + attachments=attachments, + plain_text_content = plain_text_content ) def delete_message(self, num): diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 5f3805b..8852506 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -26,4 +26,4 @@ class Email(NamedTuple): subject: str parts: List[Part] attachments: List[Attachment] - plain_text_content: str = 'no plain-text part' + plain_text_content: str From c6f1e8b82efd6b881b7d7b903966a947e8069037 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 31 Mar 2020 20:20:07 +0200 Subject: [PATCH 279/586] add type checking --- stacosys/core/cron.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 31fd697..a17af57 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -62,7 +62,7 @@ def submit_new_comment(): logger.warn('rescheduled. send mail failure ' + subject) -def _reply_comment_email(email): +def _reply_comment_email(email : Email): m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) if not m: @@ -86,15 +86,15 @@ def _reply_comment_email(email): logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return - if not email.content: + if not email.plain_text_content: logger.warn('ignore empty email') return # safe logic: no answer or unknown answer is a go for publishing - if email.content[:2].upper() in ('NO'): + if email.plain_text_content[:2].upper() in ('NO'): logger.info('discard comment: %d' % comment_id) comment.delete_instance() - new_email_body = get_template('drop_comment').render(original=email.content) + new_email_body = get_template('drop_comment').render(original=email.plain_text_content) if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): logger.warn('minor failure. cannot send rejection mail ' + email.subject) else: @@ -106,7 +106,7 @@ def _reply_comment_email(email): rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template('approve_comment').render(original=email.content) + new_email_body = get_template('approve_comment').render(original=email.plain_text_content) if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): logger.warn('minor failure. cannot send approval email ' + email.subject) From 7f6416c41a30f818c9210a79f4645609aa0b1621 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 31 Mar 2020 20:20:07 +0200 Subject: [PATCH 280/586] add type checking --- stacosys/core/cron.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 31fd697..a17af57 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -62,7 +62,7 @@ def submit_new_comment(): logger.warn('rescheduled. send mail failure ' + subject) -def _reply_comment_email(email): +def _reply_comment_email(email : Email): m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) if not m: @@ -86,15 +86,15 @@ def _reply_comment_email(email): logger.warn('ignore corrupted email. Unknown token %d' % comment_id) return - if not email.content: + if not email.plain_text_content: logger.warn('ignore empty email') return # safe logic: no answer or unknown answer is a go for publishing - if email.content[:2].upper() in ('NO'): + if email.plain_text_content[:2].upper() in ('NO'): logger.info('discard comment: %d' % comment_id) comment.delete_instance() - new_email_body = get_template('drop_comment').render(original=email.content) + new_email_body = get_template('drop_comment').render(original=email.plain_text_content) if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): logger.warn('minor failure. cannot send rejection mail ' + email.subject) else: @@ -106,7 +106,7 @@ def _reply_comment_email(email): rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template('approve_comment').render(original=email.content) + new_email_body = get_template('approve_comment').render(original=email.plain_text_content) if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): logger.warn('minor failure. cannot send approval email ' + email.subject) From 7e8930fababf50f860a709e50567f5543903c377 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Dec 2020 18:37:07 +0100 Subject: [PATCH 281/586] move package root --- .gitignore | 4 +- requirements.txt | 85 ---------------------------------- stacosys/run.py => run.py | 14 +++--- run.sh | 2 +- stacosys/__init__.py | 1 + stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/core/cron.py | 8 ++-- stacosys/core/database.py | 6 +-- stacosys/core/imap.py | 2 +- stacosys/core/mailer.py | 6 +-- stacosys/core/rss.py | 8 ++-- stacosys/core/templater.py | 2 +- stacosys/interface/__init__.py | 0 stacosys/interface/api.py | 6 +-- stacosys/interface/form.py | 6 +-- stacosys/model/__init__.py | 0 stacosys/model/comment.py | 4 +- stacosys/model/site.py | 2 +- 19 files changed, 37 insertions(+), 119 deletions(-) delete mode 100644 requirements.txt rename stacosys/run.py => run.py (88%) create mode 100644 stacosys/__init__.py create mode 100644 stacosys/conf/__init__.py create mode 100644 stacosys/core/__init__.py create mode 100644 stacosys/interface/__init__.py create mode 100644 stacosys/model/__init__.py diff --git a/.gitignore b/.gitignore index 921016e..5daab63 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,6 @@ comments.xml stacosys/bin/ stacosys/pyvenv.cfg stacosys/lib64 -.vscode/ \ No newline at end of file +.vscode/ +.pytest_cache/ +workspace.code-workspace diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a049a0c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,85 +0,0 @@ -apscheduler==3.6.3 \ - --hash=sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526 \ - --hash=sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244 -certifi==2019.11.28 \ - --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ - --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f -chardet==3.0.4 \ - --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ - --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae -click==7.0 \ - --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ - --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 -flask==1.1.1 \ - --hash=sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6 \ - --hash=sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52 -flask-apscheduler==1.11.0 \ - --hash=sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0 -idna==2.8 \ - --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ - --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 -itsdangerous==1.1.0 \ - --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ - --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 -jinja2==2.10.3 \ - --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \ - --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de -markdown==3.1.1 \ - --hash=sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c \ - --hash=sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a -markupsafe==1.1.1 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b -peewee==3.13.1 \ - --hash=sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde -profig==0.5.1 \ - --hash=sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6 -pyrss2gen==1.1 \ - --hash=sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7 -python-dateutil==2.8.1 \ - --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ - --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -pytz==2019.3 \ - --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ - --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.22.0 \ - --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 \ - --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -tzlocal==2.0.0 \ - --hash=sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048 \ - --hash=sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590 -urllib3==1.25.7 \ - --hash=sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293 \ - --hash=sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745 -werkzeug==0.16.0 \ - --hash=sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4 \ - --hash=sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7 diff --git a/stacosys/run.py b/run.py similarity index 88% rename from stacosys/run.py rename to run.py index 86fd43e..04b9d8f 100644 --- a/stacosys/run.py +++ b/run.py @@ -8,7 +8,7 @@ import os from flask import Flask from flask_apscheduler import APScheduler -from conf import config +from stacosys.conf import config # configure logging @@ -35,13 +35,13 @@ class JobConfig(object): self.JOBS = [ { 'id': 'fetch_mail', - 'func': 'core.cron:fetch_mail_answers', + 'func': 'stacosys.core.cron:fetch_mail_answers', 'trigger': 'interval', 'seconds': imap_polling_seconds, }, { 'id': 'submit_new_comment', - 'func': 'core.cron:submit_new_comment', + 'func': 'stacosys.core.cron:submit_new_comment', 'trigger': 'interval', 'seconds': new_comment_polling_seconds, }, @@ -60,7 +60,7 @@ def stacosys_server(config_pathname): logging.getLogger('apscheduler.executors').level = logging.WARNING # initialize database - from core import database + from stacosys.core import database database.setup() @@ -77,16 +77,16 @@ def stacosys_server(config_pathname): logger.info('Start Stacosys application') # generate RSS for all sites - from core import rss + from stacosys.core import rss rss.generate_all() # start Flask - from interface import api + from stacosys.interface import api logger.info('Load interface %s' % api) - from interface import form + from stacosys.interface import form logger.info('Load interface %s' % form) diff --git a/run.sh b/run.sh index fb544ec..3ee772c 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python3 stacosys/run.py "$@" +python3 run.py "$@" diff --git a/stacosys/__init__.py b/stacosys/__init__.py new file mode 100644 index 0000000..1a72d32 --- /dev/null +++ b/stacosys/__init__.py @@ -0,0 +1 @@ +__version__ = '1.1.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index a17af57..974ab17 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -6,10 +6,10 @@ import re import time from datetime import datetime -from core import mailer, rss -from core.templater import get_template -from model.comment import Comment, Site -from model.email import Email +from stacosys.core import mailer, rss +from stacosys.core.templater import get_template +from stacosys.model.comment import Comment, Site +from stacosys.model.email import Email logger = logging.getLogger(__name__) diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 2557e7e..42ea107 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -3,7 +3,7 @@ from playhouse.db_url import connect -from conf import config +from stacosys.conf import config def get_db(): @@ -11,7 +11,7 @@ def get_db(): def setup(): - from model.site import Site - from model.comment import Comment + from stacosys.model.site import Site + from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index e662da5..dd29d71 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -8,7 +8,7 @@ import imaplib import logging import re -from model.email import Attachment, Email, Part +from stacosys.model.email import Attachment, Email, Part filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 4697dc1..bf5fcc3 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -8,9 +8,9 @@ from email.mime.text import MIMEText import requests -from conf import config -from core import imap -from model.email import Email +from stacosys.conf import config +from stacosys.core import imap +from stacosys.model.email import Email logger = logging.getLogger(__name__) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index eb33272..a6c3c5d 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -6,10 +6,10 @@ from datetime import datetime import markdown import PyRSS2Gen -from conf import config -from core.templater import get_template -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.core.templater import get_template +from stacosys.model.comment import Comment +from stacosys.model.site import Site def generate_all(): diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 40b4afc..ca7b210 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -5,7 +5,7 @@ import os from jinja2 import Environment, FileSystemLoader -from conf import config +from stacosys.conf import config current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, '../templates')) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 4061278..dcc63c1 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -5,9 +5,9 @@ import logging from flask import abort, jsonify, request -from conf import config -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.model.comment import Comment +from stacosys.model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index b0f6bc2..738a958 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -6,9 +6,9 @@ from datetime import datetime from flask import abort, redirect, request -from conf import config -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.model.comment import Comment +from stacosys.model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/stacosys/model/__init__.py b/stacosys/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index ab0015f..1ebbfad 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -6,8 +6,8 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from model.site import Site -from core.database import get_db +from stacosys.model.site import Site +from stacosys.core.database import get_db from datetime import datetime diff --git a/stacosys/model/site.py b/stacosys/model/site.py index 156ebb3..bbd6091 100644 --- a/stacosys/model/site.py +++ b/stacosys/model/site.py @@ -3,7 +3,7 @@ from peewee import Model from peewee import CharField -from core.database import get_db +from stacosys.core.database import get_db class Site(Model): From eaad39b05b15c9f3d24a70dd45367c7fbf0b4711 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 27 Dec 2020 18:37:07 +0100 Subject: [PATCH 282/586] move package root --- .gitignore | 4 +- requirements.txt | 85 ---------------------------------- stacosys/run.py => run.py | 14 +++--- run.sh | 2 +- stacosys/__init__.py | 1 + stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/core/cron.py | 8 ++-- stacosys/core/database.py | 6 +-- stacosys/core/imap.py | 2 +- stacosys/core/mailer.py | 6 +-- stacosys/core/rss.py | 8 ++-- stacosys/core/templater.py | 2 +- stacosys/interface/__init__.py | 0 stacosys/interface/api.py | 6 +-- stacosys/interface/form.py | 6 +-- stacosys/model/__init__.py | 0 stacosys/model/comment.py | 4 +- stacosys/model/site.py | 2 +- 19 files changed, 37 insertions(+), 119 deletions(-) delete mode 100644 requirements.txt rename stacosys/run.py => run.py (88%) create mode 100644 stacosys/__init__.py create mode 100644 stacosys/conf/__init__.py create mode 100644 stacosys/core/__init__.py create mode 100644 stacosys/interface/__init__.py create mode 100644 stacosys/model/__init__.py diff --git a/.gitignore b/.gitignore index 921016e..5daab63 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,6 @@ comments.xml stacosys/bin/ stacosys/pyvenv.cfg stacosys/lib64 -.vscode/ \ No newline at end of file +.vscode/ +.pytest_cache/ +workspace.code-workspace diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a049a0c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,85 +0,0 @@ -apscheduler==3.6.3 \ - --hash=sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526 \ - --hash=sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244 -certifi==2019.11.28 \ - --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ - --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f -chardet==3.0.4 \ - --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ - --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae -click==7.0 \ - --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ - --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 -flask==1.1.1 \ - --hash=sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6 \ - --hash=sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52 -flask-apscheduler==1.11.0 \ - --hash=sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0 -idna==2.8 \ - --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ - --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 -itsdangerous==1.1.0 \ - --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ - --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 -jinja2==2.10.3 \ - --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \ - --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de -markdown==3.1.1 \ - --hash=sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c \ - --hash=sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a -markupsafe==1.1.1 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b -peewee==3.13.1 \ - --hash=sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde -profig==0.5.1 \ - --hash=sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6 -pyrss2gen==1.1 \ - --hash=sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7 -python-dateutil==2.8.1 \ - --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ - --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -pytz==2019.3 \ - --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ - --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.22.0 \ - --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 \ - --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -tzlocal==2.0.0 \ - --hash=sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048 \ - --hash=sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590 -urllib3==1.25.7 \ - --hash=sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293 \ - --hash=sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745 -werkzeug==0.16.0 \ - --hash=sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4 \ - --hash=sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7 diff --git a/stacosys/run.py b/run.py similarity index 88% rename from stacosys/run.py rename to run.py index 86fd43e..04b9d8f 100644 --- a/stacosys/run.py +++ b/run.py @@ -8,7 +8,7 @@ import os from flask import Flask from flask_apscheduler import APScheduler -from conf import config +from stacosys.conf import config # configure logging @@ -35,13 +35,13 @@ class JobConfig(object): self.JOBS = [ { 'id': 'fetch_mail', - 'func': 'core.cron:fetch_mail_answers', + 'func': 'stacosys.core.cron:fetch_mail_answers', 'trigger': 'interval', 'seconds': imap_polling_seconds, }, { 'id': 'submit_new_comment', - 'func': 'core.cron:submit_new_comment', + 'func': 'stacosys.core.cron:submit_new_comment', 'trigger': 'interval', 'seconds': new_comment_polling_seconds, }, @@ -60,7 +60,7 @@ def stacosys_server(config_pathname): logging.getLogger('apscheduler.executors').level = logging.WARNING # initialize database - from core import database + from stacosys.core import database database.setup() @@ -77,16 +77,16 @@ def stacosys_server(config_pathname): logger.info('Start Stacosys application') # generate RSS for all sites - from core import rss + from stacosys.core import rss rss.generate_all() # start Flask - from interface import api + from stacosys.interface import api logger.info('Load interface %s' % api) - from interface import form + from stacosys.interface import form logger.info('Load interface %s' % form) diff --git a/run.sh b/run.sh index fb544ec..3ee772c 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python3 stacosys/run.py "$@" +python3 run.py "$@" diff --git a/stacosys/__init__.py b/stacosys/__init__.py new file mode 100644 index 0000000..1a72d32 --- /dev/null +++ b/stacosys/__init__.py @@ -0,0 +1 @@ +__version__ = '1.1.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index a17af57..974ab17 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -6,10 +6,10 @@ import re import time from datetime import datetime -from core import mailer, rss -from core.templater import get_template -from model.comment import Comment, Site -from model.email import Email +from stacosys.core import mailer, rss +from stacosys.core.templater import get_template +from stacosys.model.comment import Comment, Site +from stacosys.model.email import Email logger = logging.getLogger(__name__) diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 2557e7e..42ea107 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -3,7 +3,7 @@ from playhouse.db_url import connect -from conf import config +from stacosys.conf import config def get_db(): @@ -11,7 +11,7 @@ def get_db(): def setup(): - from model.site import Site - from model.comment import Comment + from stacosys.model.site import Site + from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index e662da5..dd29d71 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -8,7 +8,7 @@ import imaplib import logging import re -from model.email import Attachment, Email, Part +from stacosys.model.email import Attachment, Email, Part filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 4697dc1..bf5fcc3 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -8,9 +8,9 @@ from email.mime.text import MIMEText import requests -from conf import config -from core import imap -from model.email import Email +from stacosys.conf import config +from stacosys.core import imap +from stacosys.model.email import Email logger = logging.getLogger(__name__) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index eb33272..a6c3c5d 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -6,10 +6,10 @@ from datetime import datetime import markdown import PyRSS2Gen -from conf import config -from core.templater import get_template -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.core.templater import get_template +from stacosys.model.comment import Comment +from stacosys.model.site import Site def generate_all(): diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 40b4afc..ca7b210 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -5,7 +5,7 @@ import os from jinja2 import Environment, FileSystemLoader -from conf import config +from stacosys.conf import config current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, '../templates')) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 4061278..dcc63c1 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -5,9 +5,9 @@ import logging from flask import abort, jsonify, request -from conf import config -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.model.comment import Comment +from stacosys.model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index b0f6bc2..738a958 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -6,9 +6,9 @@ from datetime import datetime from flask import abort, redirect, request -from conf import config -from model.comment import Comment -from model.site import Site +from stacosys.conf import config +from stacosys.model.comment import Comment +from stacosys.model.site import Site logger = logging.getLogger(__name__) app = config.flaskapp() diff --git a/stacosys/model/__init__.py b/stacosys/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index ab0015f..1ebbfad 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -6,8 +6,8 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from model.site import Site -from core.database import get_db +from stacosys.model.site import Site +from stacosys.core.database import get_db from datetime import datetime diff --git a/stacosys/model/site.py b/stacosys/model/site.py index 156ebb3..bbd6091 100644 --- a/stacosys/model/site.py +++ b/stacosys/model/site.py @@ -3,7 +3,7 @@ from peewee import Model from peewee import CharField -from core.database import get_db +from stacosys.core.database import get_db class Site(Model): From b258194d6f25c0bfc067aa04d736f39b8af1d38c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Dec 2020 20:04:20 +0100 Subject: [PATCH 283/586] tinydb migration --- poetry.lock | 328 ++++++++++++++++++++------------------ pyproject.toml | 1 + run.py | 3 +- stacosys/core/database.py | 21 +++ 4 files changed, 195 insertions(+), 158 deletions(-) diff --git a/poetry.lock b/poetry.lock index 760ad71..997c104 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,22 +1,21 @@ [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.3" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.3" [[package]] -category = "main" -description = "In-process task scheduler with Cron-like capabilities" name = "apscheduler" +version = "3.6.3" +description = "In-process task scheduler with Cron-like capabilities" +category = "main" optional = false python-versions = "*" -version = "3.6.3" [package.dependencies] pytz = "*" -setuptools = ">=0.7" six = ">=1.4.0" tzlocal = ">=1.2" @@ -34,21 +33,20 @@ twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.3.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "19.3.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" [package.extras] azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] @@ -57,12 +55,12 @@ docs = ["sphinx", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "19.10b0" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "19.10b0" [package.dependencies] appdirs = "*" @@ -77,51 +75,50 @@ typed-ast = ">=1.4.0" d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" -optional = false -python-versions = "*" version = "2019.11.28" - -[[package]] +description = "Python package for providing Mozilla's CA Bundle." category = "main" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" optional = false python-versions = "*" -version = "3.0.4" [[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" category = "main" -description = "Composable command line interface toolkit" +optional = false +python-versions = "*" + +[[package]] name = "click" +version = "7.0" +description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "7.0" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.3" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" [[package]] -category = "main" -description = "A simple framework for building complex web applications." name = "flask" +version = "1.1.1" +description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.1.1" [package.dependencies] -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" click = ">=5.1" itsdangerous = ">=0.24" +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -129,12 +126,12 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx- dotenv = ["python-dotenv"] [[package]] -category = "main" -description = "Adds APScheduler support to Flask" name = "flask-apscheduler" +version = "1.11.0" +description = "Adds APScheduler support to Flask" +category = "main" optional = false python-versions = "*" -version = "1.11.0" [package.dependencies] apscheduler = ">=3.2.0" @@ -142,21 +139,20 @@ flask = ">=0.10.1" python-dateutil = ">=2.4.2" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.8" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "1.3.0" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.3.0" [package.dependencies] zipp = ">=0.5" @@ -166,20 +162,20 @@ docs = ["sphinx", "rst.linker"] testing = ["packaging", "importlib-resources"] [[package]] -category = "main" -description = "Various helpers to pass data to untrusted environments and back." name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.10.3" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = "*" -version = "2.10.3" [package.dependencies] MarkupSafe = ">=0.23" @@ -188,170 +184,162 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "Python implementation of Markdown." name = "markdown" +version = "3.1.1" +description = "Python implementation of Markdown." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "3.1.1" - -[package.dependencies] -setuptools = ">=36" [package.extras] testing = ["coverage", "pyyaml"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" +version = "8.0.2" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" optional = false python-versions = ">=3.5" -version = "8.0.2" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "19.2" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.2" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.7.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.7.0" [[package]] -category = "main" -description = "a little orm" name = "peewee" +version = "3.13.1" +description = "a little orm" +category = "main" optional = false python-versions = "*" -version = "3.13.1" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "main" -description = "A configuration library." name = "profig" +version = "0.5.1" +description = "A configuration library." +category = "main" optional = false python-versions = "*" -version = "0.5.1" [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.8.1" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.1" [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.6" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.6" [[package]] -category = "main" -description = "Generate RSS2 using a Python data structure" name = "pyrss2gen" +version = "1.1" +description = "Generate RSS2 using a Python data structure" +category = "main" optional = false python-versions = "*" -version = "1.1" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "5.3.2" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.3.2" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" -optional = false -python-versions = "*" version = "2019.3" - -[[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." -name = "regex" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "regex" +version = "2020.2.20" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.2.20" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.22.0" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.22.0" [package.dependencies] certifi = ">=2017.4.17" @@ -361,82 +349,90 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "a python refactoring library..." name = "rope" +version = "0.16.0" +description = "a python refactoring library..." +category = "dev" optional = false python-versions = "*" -version = "0.16.0" [package.extras] dev = ["pytest"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.13.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.13.0" - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.0" - -[[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" [[package]] +name = "tinydb" +version = "4.3.0" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" -description = "tzinfo object for the local timezone" -name = "tzlocal" +optional = false +python-versions = ">=3.5,<4.0" + +[[package]] +name = "toml" +version = "0.10.0" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = "*" + +[[package]] +name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "tzlocal" version = "2.0.0" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = "*" [package.dependencies] pytz = "*" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.25.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.7" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" name = "wcwidth" +version = "0.1.7" +description = "Measures number of Terminal column cells of wide-character codes" +category = "dev" optional = false python-versions = "*" -version = "0.1.7" [[package]] -category = "main" -description = "The comprehensive WSGI web application library." name = "werkzeug" +version = "0.16.0" +description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.16.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] @@ -444,13 +440,12 @@ termcolor = ["termcolor"] watchdog = ["watchdog"] [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "0.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=2.7" -version = "0.6.0" [package.dependencies] more-itertools = "*" @@ -460,8 +455,9 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "d698fc06cf58f4d228449cb76f48e8d739c323abd535427746d70dbbcaa4924c" +lock-version = "1.1" python-versions = "^3.7" +content-hash = "6a714b61f2ae04be8377aa2fb513c09bdae22cb71685b4b7ddfed843bfd9ff08" [metadata.files] appdirs = [ @@ -555,6 +551,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ @@ -638,6 +639,10 @@ six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, ] +tinydb = [ + {file = "tinydb-4.3.0-py3-none-any.whl", hash = "sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"}, + {file = "tinydb-4.3.0.tar.gz", hash = "sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303"}, +] toml = [ {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, @@ -651,19 +656,28 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, + {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, + {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] tzlocal = [ diff --git a/pyproject.toml b/pyproject.toml index 2df28ab..99ae291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" requests = "^2.22.0" +tinydb = "^4.3.0" [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/run.py b/run.py index 04b9d8f..6fd1a19 100644 --- a/run.py +++ b/run.py @@ -63,7 +63,8 @@ def stacosys_server(config_pathname): from stacosys.core import database database.setup() - + database.dump_db() + # cron email fetcher app.config.from_object( JobConfig( diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 42ea107..a50165b 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -15,3 +15,24 @@ def setup(): from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) + +from playhouse.shortcuts import model_to_dict +import json + +def tojson_model(comment): + dcomment = model_to_dict(comment) + del dcomment['site'] + tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) + return json.loads(tcomment) + +def tojson_models(models): + print(json.dumps(list(models.dicts()), indent=4, sort_keys=True, default=str)) + +def dump_db(): + from tinydb import TinyDB, Query + from stacosys.model.comment import Comment + db = TinyDB('db.json') + for comment in Comment.select(): + cc = tojson_model(comment) + print(cc) + db.insert(cc) From 3293a10abe372e27a0e4eb25cbb2169151dacda1 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 27 Dec 2020 20:04:20 +0100 Subject: [PATCH 284/586] tinydb migration --- poetry.lock | 328 ++++++++++++++++++++------------------ pyproject.toml | 1 + run.py | 3 +- stacosys/core/database.py | 21 +++ 4 files changed, 195 insertions(+), 158 deletions(-) diff --git a/poetry.lock b/poetry.lock index 760ad71..997c104 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,22 +1,21 @@ [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.3" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.3" [[package]] -category = "main" -description = "In-process task scheduler with Cron-like capabilities" name = "apscheduler" +version = "3.6.3" +description = "In-process task scheduler with Cron-like capabilities" +category = "main" optional = false python-versions = "*" -version = "3.6.3" [package.dependencies] pytz = "*" -setuptools = ">=0.7" six = ">=1.4.0" tzlocal = ">=1.2" @@ -34,21 +33,20 @@ twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.3.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "19.3.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" [package.extras] azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] @@ -57,12 +55,12 @@ docs = ["sphinx", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "19.10b0" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "19.10b0" [package.dependencies] appdirs = "*" @@ -77,51 +75,50 @@ typed-ast = ">=1.4.0" d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" -optional = false -python-versions = "*" version = "2019.11.28" - -[[package]] +description = "Python package for providing Mozilla's CA Bundle." category = "main" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" optional = false python-versions = "*" -version = "3.0.4" [[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" category = "main" -description = "Composable command line interface toolkit" +optional = false +python-versions = "*" + +[[package]] name = "click" +version = "7.0" +description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "7.0" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.3" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" [[package]] -category = "main" -description = "A simple framework for building complex web applications." name = "flask" +version = "1.1.1" +description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.1.1" [package.dependencies] -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" click = ">=5.1" itsdangerous = ">=0.24" +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -129,12 +126,12 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx- dotenv = ["python-dotenv"] [[package]] -category = "main" -description = "Adds APScheduler support to Flask" name = "flask-apscheduler" +version = "1.11.0" +description = "Adds APScheduler support to Flask" +category = "main" optional = false python-versions = "*" -version = "1.11.0" [package.dependencies] apscheduler = ">=3.2.0" @@ -142,21 +139,20 @@ flask = ">=0.10.1" python-dateutil = ">=2.4.2" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.8" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "1.3.0" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.3.0" [package.dependencies] zipp = ">=0.5" @@ -166,20 +162,20 @@ docs = ["sphinx", "rst.linker"] testing = ["packaging", "importlib-resources"] [[package]] -category = "main" -description = "Various helpers to pass data to untrusted environments and back." name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" [[package]] -category = "main" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.10.3" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = "*" -version = "2.10.3" [package.dependencies] MarkupSafe = ">=0.23" @@ -188,170 +184,162 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "Python implementation of Markdown." name = "markdown" +version = "3.1.1" +description = "Python implementation of Markdown." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "3.1.1" - -[package.dependencies] -setuptools = ">=36" [package.extras] testing = ["coverage", "pyyaml"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" +version = "8.0.2" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" optional = false python-versions = ">=3.5" -version = "8.0.2" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "19.2" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.2" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.7.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.7.0" [[package]] -category = "main" -description = "a little orm" name = "peewee" +version = "3.13.1" +description = "a little orm" +category = "main" optional = false python-versions = "*" -version = "3.13.1" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "main" -description = "A configuration library." name = "profig" +version = "0.5.1" +description = "A configuration library." +category = "main" optional = false python-versions = "*" -version = "0.5.1" [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.8.1" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.1" [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.6" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.6" [[package]] -category = "main" -description = "Generate RSS2 using a Python data structure" name = "pyrss2gen" +version = "1.1" +description = "Generate RSS2 using a Python data structure" +category = "main" optional = false python-versions = "*" -version = "1.1" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "5.3.2" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.3.2" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" -optional = false -python-versions = "*" version = "2019.3" - -[[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." -name = "regex" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "regex" +version = "2020.2.20" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.2.20" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.22.0" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.22.0" [package.dependencies] certifi = ">=2017.4.17" @@ -361,82 +349,90 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "a python refactoring library..." name = "rope" +version = "0.16.0" +description = "a python refactoring library..." +category = "dev" optional = false python-versions = "*" -version = "0.16.0" [package.extras] dev = ["pytest"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.13.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.13.0" - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.0" - -[[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" [[package]] +name = "tinydb" +version = "4.3.0" +description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" -description = "tzinfo object for the local timezone" -name = "tzlocal" +optional = false +python-versions = ">=3.5,<4.0" + +[[package]] +name = "toml" +version = "0.10.0" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = "*" + +[[package]] +name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "tzlocal" version = "2.0.0" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = "*" [package.dependencies] pytz = "*" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.25.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.7" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" name = "wcwidth" +version = "0.1.7" +description = "Measures number of Terminal column cells of wide-character codes" +category = "dev" optional = false python-versions = "*" -version = "0.1.7" [[package]] -category = "main" -description = "The comprehensive WSGI web application library." name = "werkzeug" +version = "0.16.0" +description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.16.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] @@ -444,13 +440,12 @@ termcolor = ["termcolor"] watchdog = ["watchdog"] [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "0.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=2.7" -version = "0.6.0" [package.dependencies] more-itertools = "*" @@ -460,8 +455,9 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] -content-hash = "d698fc06cf58f4d228449cb76f48e8d739c323abd535427746d70dbbcaa4924c" +lock-version = "1.1" python-versions = "^3.7" +content-hash = "6a714b61f2ae04be8377aa2fb513c09bdae22cb71685b4b7ddfed843bfd9ff08" [metadata.files] appdirs = [ @@ -555,6 +551,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ @@ -638,6 +639,10 @@ six = [ {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, ] +tinydb = [ + {file = "tinydb-4.3.0-py3-none-any.whl", hash = "sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"}, + {file = "tinydb-4.3.0.tar.gz", hash = "sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303"}, +] toml = [ {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, @@ -651,19 +656,28 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, + {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, + {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] tzlocal = [ diff --git a/pyproject.toml b/pyproject.toml index 2df28ab..99ae291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" requests = "^2.22.0" +tinydb = "^4.3.0" [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/run.py b/run.py index 04b9d8f..6fd1a19 100644 --- a/run.py +++ b/run.py @@ -63,7 +63,8 @@ def stacosys_server(config_pathname): from stacosys.core import database database.setup() - + database.dump_db() + # cron email fetcher app.config.from_object( JobConfig( diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 42ea107..a50165b 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -15,3 +15,24 @@ def setup(): from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) + +from playhouse.shortcuts import model_to_dict +import json + +def tojson_model(comment): + dcomment = model_to_dict(comment) + del dcomment['site'] + tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) + return json.loads(tcomment) + +def tojson_models(models): + print(json.dumps(list(models.dicts()), indent=4, sort_keys=True, default=str)) + +def dump_db(): + from tinydb import TinyDB, Query + from stacosys.model.comment import Comment + db = TinyDB('db.json') + for comment in Comment.select(): + cc = tojson_model(comment) + print(cc) + db.insert(cc) From aa122f3138a12aaabe369e9a82bc83e5437ba7a9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 28 Dec 2020 19:43:23 +0100 Subject: [PATCH 285/586] draft tinydb persistence --- config.ini | 7 +++++++ stacosys/conf/config.py | 5 +++++ stacosys/core/database.py | 15 +++++++++------ stacosys/core/persistence.py | 22 ++++++++++++++++++++++ tests/test_persistence.py | 20 ++++++++++++++++++++ tests/test_stacosys.py | 2 +- 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 stacosys/core/persistence.py create mode 100644 tests/test_persistence.py diff --git a/config.ini b/config.ini index 894d434..8d7b7e2 100755 --- a/config.ini +++ b/config.ini @@ -4,6 +4,13 @@ lang = fr db_url = sqlite:///db.sqlite newcomment_polling = 60 +db_file = db.json + +[site] +name = "My blog" +url = http://blog.mydomain.com +token = aabbccddeeffgghhiijjkkllmm +admin_email = admin@mydomain.com [http] host = 127.0.0.1 diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 712a77f..a4742d3 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -7,6 +7,7 @@ import profig FLASK_APP = 'flask.app' DB_URL = 'main.db_url' +DB_FILE = 'main.db_file' LANG = 'main.lang' COMMENT_POLLING = 'main.newcomment_polling' @@ -29,6 +30,10 @@ SMTP_PORT = 'smtp.port' SMTP_LOGIN = 'smtp.login' SMTP_PASSWORD = 'smtp.password' +SITE_NAME = 'site.name' +SITE_URL = 'site.url' +SITE_TOKEN = 'site.token' +SITE_ADMIN_EMAIL = 'site.admin_email' # variable params = dict() diff --git a/stacosys/core/database.py b/stacosys/core/database.py index a50165b..5be8287 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -16,23 +16,26 @@ def setup(): get_db().create_tables([Site, Comment], safe=True) + from playhouse.shortcuts import model_to_dict import json + def tojson_model(comment): dcomment = model_to_dict(comment) - del dcomment['site'] + del dcomment["site"] tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) - return json.loads(tcomment) + return json.loads(tcomment) -def tojson_models(models): - print(json.dumps(list(models.dicts()), indent=4, sort_keys=True, default=str)) def dump_db(): from tinydb import TinyDB, Query from stacosys.model.comment import Comment - db = TinyDB('db.json') + + db = TinyDB("db.json", sort_keys=True, indent=4, separators=(",", ": ")) + db.drop_tables() + table = db.table("comments") for comment in Comment.select(): cc = tojson_model(comment) print(cc) - db.insert(cc) + table.insert(cc) diff --git a/stacosys/core/persistence.py b/stacosys/core/persistence.py new file mode 100644 index 0000000..32351dd --- /dev/null +++ b/stacosys/core/persistence.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from tinydb import TinyDB +from tinydb.storages import MemoryStorage + +from stacosys.conf import config + + +class Persistence: + def __init__(self): + db_file = config.get(config.DB_FILE) + if db_file: + self.db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) + else: + self.db = TinyDB(storage=MemoryStorage) + + def get_db(self): + return self.db + + def get_table_comments(self): + return self.db.table("comments") diff --git a/tests/test_persistence.py b/tests/test_persistence.py new file mode 100644 index 0000000..d4dd92d --- /dev/null +++ b/tests/test_persistence.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest + + +@pytest.fixture +def persistence(): + from stacosys.conf import config + + config.params = {"main.db_file": None} + from stacosys.core import persistence + + return persistence.Persistence() + + +def test_init_persistence(persistence): + assert persistence is not None + assert persistence.get_db() is not None + assert persistence.get_table_comments() is not None diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index f0d2e64..124b4fa 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '0.1.0' + assert __version__ == '1.1.0' From 674811b77f73476b3e483170d54395ca0e8c32d7 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 28 Dec 2020 19:43:23 +0100 Subject: [PATCH 286/586] draft tinydb persistence --- config.ini | 7 +++++++ stacosys/conf/config.py | 5 +++++ stacosys/core/database.py | 15 +++++++++------ stacosys/core/persistence.py | 22 ++++++++++++++++++++++ tests/test_persistence.py | 20 ++++++++++++++++++++ tests/test_stacosys.py | 2 +- 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 stacosys/core/persistence.py create mode 100644 tests/test_persistence.py diff --git a/config.ini b/config.ini index 894d434..8d7b7e2 100755 --- a/config.ini +++ b/config.ini @@ -4,6 +4,13 @@ lang = fr db_url = sqlite:///db.sqlite newcomment_polling = 60 +db_file = db.json + +[site] +name = "My blog" +url = http://blog.mydomain.com +token = aabbccddeeffgghhiijjkkllmm +admin_email = admin@mydomain.com [http] host = 127.0.0.1 diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 712a77f..a4742d3 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -7,6 +7,7 @@ import profig FLASK_APP = 'flask.app' DB_URL = 'main.db_url' +DB_FILE = 'main.db_file' LANG = 'main.lang' COMMENT_POLLING = 'main.newcomment_polling' @@ -29,6 +30,10 @@ SMTP_PORT = 'smtp.port' SMTP_LOGIN = 'smtp.login' SMTP_PASSWORD = 'smtp.password' +SITE_NAME = 'site.name' +SITE_URL = 'site.url' +SITE_TOKEN = 'site.token' +SITE_ADMIN_EMAIL = 'site.admin_email' # variable params = dict() diff --git a/stacosys/core/database.py b/stacosys/core/database.py index a50165b..5be8287 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -16,23 +16,26 @@ def setup(): get_db().create_tables([Site, Comment], safe=True) + from playhouse.shortcuts import model_to_dict import json + def tojson_model(comment): dcomment = model_to_dict(comment) - del dcomment['site'] + del dcomment["site"] tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) - return json.loads(tcomment) + return json.loads(tcomment) -def tojson_models(models): - print(json.dumps(list(models.dicts()), indent=4, sort_keys=True, default=str)) def dump_db(): from tinydb import TinyDB, Query from stacosys.model.comment import Comment - db = TinyDB('db.json') + + db = TinyDB("db.json", sort_keys=True, indent=4, separators=(",", ": ")) + db.drop_tables() + table = db.table("comments") for comment in Comment.select(): cc = tojson_model(comment) print(cc) - db.insert(cc) + table.insert(cc) diff --git a/stacosys/core/persistence.py b/stacosys/core/persistence.py new file mode 100644 index 0000000..32351dd --- /dev/null +++ b/stacosys/core/persistence.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from tinydb import TinyDB +from tinydb.storages import MemoryStorage + +from stacosys.conf import config + + +class Persistence: + def __init__(self): + db_file = config.get(config.DB_FILE) + if db_file: + self.db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) + else: + self.db = TinyDB(storage=MemoryStorage) + + def get_db(self): + return self.db + + def get_table_comments(self): + return self.db.table("comments") diff --git a/tests/test_persistence.py b/tests/test_persistence.py new file mode 100644 index 0000000..d4dd92d --- /dev/null +++ b/tests/test_persistence.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest + + +@pytest.fixture +def persistence(): + from stacosys.conf import config + + config.params = {"main.db_file": None} + from stacosys.core import persistence + + return persistence.Persistence() + + +def test_init_persistence(persistence): + assert persistence is not None + assert persistence.get_db() is not None + assert persistence.get_table_comments() is not None diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index f0d2e64..124b4fa 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '0.1.0' + assert __version__ == '1.1.0' From 19112245c0ce90af60c0165dc3fe43b6b34944cd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 28 Dec 2020 19:44:26 +0100 Subject: [PATCH 287/586] git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5daab63..d5ee1fc 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ target/ # My ignored files and directories myconfig.json db.sqlite +db.json node_modules comments.xml stacosys/bin/ From 5bf6040d2cc2afee7351394f931769c3311d9c26 Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 28 Dec 2020 19:44:26 +0100 Subject: [PATCH 288/586] git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5daab63..d5ee1fc 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ target/ # My ignored files and directories myconfig.json db.sqlite +db.json node_modules comments.xml stacosys/bin/ From b09a576b4bbd702b115f81d1b1eac9828c9056d3 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 1 Jan 2021 11:17:09 +0100 Subject: [PATCH 289/586] config becomes object-oriented and pytests have been added --- stacosys/conf/config.py | 85 +++++++++++++++++++++-------------------- tests/test_config.py | 57 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 tests/test_config.py diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a4742d3..15a0ed2 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,59 +4,62 @@ import profig # constants -FLASK_APP = 'flask.app' +FLASK_APP = "flask.app" -DB_URL = 'main.db_url' -DB_FILE = 'main.db_file' -LANG = 'main.lang' -COMMENT_POLLING = 'main.newcomment_polling' +DB_URL = "main.db_url" +DB_BACKUP_JSON_FILE = "main.db_backup_json_file" +LANG = "main.lang" +COMMENT_POLLING = "main.newcomment_polling" -HTTP_HOST = 'http.host' -HTTP_PORT = 'http.port' +HTTP_HOST = "http.host" +HTTP_PORT = "http.port" -RSS_PROTO = 'rss.proto' -RSS_FILE = 'rss.file' +RSS_PROTO = "rss.proto" +RSS_FILE = "rss.file" -IMAP_POLLING = 'imap.polling' -IMAP_SSL = 'imap.ssl' -IMAP_HOST = 'imap.host' -IMAP_PORT = 'imap.port' -IMAP_LOGIN = 'imap.login' -IMAP_PASSWORD = 'imap.password' +IMAP_POLLING = "imap.polling" +IMAP_SSL = "imap.ssl" +IMAP_HOST = "imap.host" +IMAP_PORT = "imap.port" +IMAP_LOGIN = "imap.login" +IMAP_PASSWORD = "imap.password" -SMTP_STARTTLS = 'smtp.starttls' -SMTP_HOST = 'smtp.host' -SMTP_PORT = 'smtp.port' -SMTP_LOGIN = 'smtp.login' -SMTP_PASSWORD = 'smtp.password' +SMTP_STARTTLS = "smtp.starttls" +SMTP_HOST = "smtp.host" +SMTP_PORT = "smtp.port" +SMTP_LOGIN = "smtp.login" +SMTP_PASSWORD = "smtp.password" -SITE_NAME = 'site.name' -SITE_URL = 'site.url' -SITE_TOKEN = 'site.token' -SITE_ADMIN_EMAIL = 'site.admin_email' - -# variable -params = dict() +SITE_NAME = "site.name" +SITE_URL = "site.url" +SITE_TOKEN = "site.token" +SITE_ADMIN_EMAIL = "site.admin_email" -def initialize(config_pathname, flask_app): - cfg = profig.Config(config_pathname) - cfg.sync() - params.update(cfg) - params.update({FLASK_APP: flask_app}) +class Config: + def __init__(self): + self._params = dict() + @classmethod + def load(cls, config_pathname): + cfg = profig.Config(config_pathname) + cfg.sync() + config = cls() + config._params.update(cfg) + return config -def get(key): - return params[key] + def exists(self, key): + return key in self._params + def get(self, key): + return self._params[key] if key in self._params else None -def get_int(key): - return int(params[key]) + def put(self, key, value): + self._params[key] = value + def get_int(self, key): + return int(self._params[key]) -def get_bool(key): - return params[key].lower() in ('yes', 'true', '1') + def get_bool(self, key): + return self._params[key].lower() in ("yes", "true") - -def flaskapp(): - return params[FLASK_APP] diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..4b6d6f0 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest +import stacosys.conf.config as config + +EXPECTED_DB_URL = "sqlite:///db.sqlite" +EXPECTED_HTTP_PORT = 8080 +EXPECTED_IMAP_PORT = "5000" +EXPECTED_IMAP_LOGIN = "user" + + +@pytest.fixture +def conf(): + conf = config.Config() + conf.put(config.DB_URL, EXPECTED_DB_URL) + conf.put(config.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(config.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(config.SMTP_STARTTLS, "yes") + conf.put(config.IMAP_SSL, "false") + return conf + + +def test_exists(conf): + assert conf is not None + assert conf.exists(config.DB_URL) + assert not conf.exists(config.IMAP_HOST) + + +def test_get(conf): + assert conf is not None + assert conf.get(config.DB_URL) == EXPECTED_DB_URL + assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(config.HTTP_HOST) is None + assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(config.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(config.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + try: + conf.get_int(config.HTTP_PORT) + assert False + except: + pass + assert conf.get_bool(config.SMTP_STARTTLS) + assert not conf.get_bool(config.IMAP_SSL) + try: + conf.get_bool(config.DB_URL) + assert False + except: + pass + + +def test_put(conf): + assert conf is not None + assert not conf.exists(config.IMAP_LOGIN) + conf.put(config.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(config.IMAP_LOGIN) + assert conf.get(config.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From 9b578637bacbb695099f286d144e2865758c6b92 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 1 Jan 2021 11:17:09 +0100 Subject: [PATCH 290/586] config becomes object-oriented and pytests have been added --- stacosys/conf/config.py | 85 +++++++++++++++++++++-------------------- tests/test_config.py | 57 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 tests/test_config.py diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a4742d3..15a0ed2 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,59 +4,62 @@ import profig # constants -FLASK_APP = 'flask.app' +FLASK_APP = "flask.app" -DB_URL = 'main.db_url' -DB_FILE = 'main.db_file' -LANG = 'main.lang' -COMMENT_POLLING = 'main.newcomment_polling' +DB_URL = "main.db_url" +DB_BACKUP_JSON_FILE = "main.db_backup_json_file" +LANG = "main.lang" +COMMENT_POLLING = "main.newcomment_polling" -HTTP_HOST = 'http.host' -HTTP_PORT = 'http.port' +HTTP_HOST = "http.host" +HTTP_PORT = "http.port" -RSS_PROTO = 'rss.proto' -RSS_FILE = 'rss.file' +RSS_PROTO = "rss.proto" +RSS_FILE = "rss.file" -IMAP_POLLING = 'imap.polling' -IMAP_SSL = 'imap.ssl' -IMAP_HOST = 'imap.host' -IMAP_PORT = 'imap.port' -IMAP_LOGIN = 'imap.login' -IMAP_PASSWORD = 'imap.password' +IMAP_POLLING = "imap.polling" +IMAP_SSL = "imap.ssl" +IMAP_HOST = "imap.host" +IMAP_PORT = "imap.port" +IMAP_LOGIN = "imap.login" +IMAP_PASSWORD = "imap.password" -SMTP_STARTTLS = 'smtp.starttls' -SMTP_HOST = 'smtp.host' -SMTP_PORT = 'smtp.port' -SMTP_LOGIN = 'smtp.login' -SMTP_PASSWORD = 'smtp.password' +SMTP_STARTTLS = "smtp.starttls" +SMTP_HOST = "smtp.host" +SMTP_PORT = "smtp.port" +SMTP_LOGIN = "smtp.login" +SMTP_PASSWORD = "smtp.password" -SITE_NAME = 'site.name' -SITE_URL = 'site.url' -SITE_TOKEN = 'site.token' -SITE_ADMIN_EMAIL = 'site.admin_email' - -# variable -params = dict() +SITE_NAME = "site.name" +SITE_URL = "site.url" +SITE_TOKEN = "site.token" +SITE_ADMIN_EMAIL = "site.admin_email" -def initialize(config_pathname, flask_app): - cfg = profig.Config(config_pathname) - cfg.sync() - params.update(cfg) - params.update({FLASK_APP: flask_app}) +class Config: + def __init__(self): + self._params = dict() + @classmethod + def load(cls, config_pathname): + cfg = profig.Config(config_pathname) + cfg.sync() + config = cls() + config._params.update(cfg) + return config -def get(key): - return params[key] + def exists(self, key): + return key in self._params + def get(self, key): + return self._params[key] if key in self._params else None -def get_int(key): - return int(params[key]) + def put(self, key, value): + self._params[key] = value + def get_int(self, key): + return int(self._params[key]) -def get_bool(key): - return params[key].lower() in ('yes', 'true', '1') + def get_bool(self, key): + return self._params[key].lower() in ("yes", "true") - -def flaskapp(): - return params[FLASK_APP] diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..4b6d6f0 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest +import stacosys.conf.config as config + +EXPECTED_DB_URL = "sqlite:///db.sqlite" +EXPECTED_HTTP_PORT = 8080 +EXPECTED_IMAP_PORT = "5000" +EXPECTED_IMAP_LOGIN = "user" + + +@pytest.fixture +def conf(): + conf = config.Config() + conf.put(config.DB_URL, EXPECTED_DB_URL) + conf.put(config.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(config.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(config.SMTP_STARTTLS, "yes") + conf.put(config.IMAP_SSL, "false") + return conf + + +def test_exists(conf): + assert conf is not None + assert conf.exists(config.DB_URL) + assert not conf.exists(config.IMAP_HOST) + + +def test_get(conf): + assert conf is not None + assert conf.get(config.DB_URL) == EXPECTED_DB_URL + assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(config.HTTP_HOST) is None + assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(config.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(config.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + try: + conf.get_int(config.HTTP_PORT) + assert False + except: + pass + assert conf.get_bool(config.SMTP_STARTTLS) + assert not conf.get_bool(config.IMAP_SSL) + try: + conf.get_bool(config.DB_URL) + assert False + except: + pass + + +def test_put(conf): + assert conf is not None + assert not conf.exists(config.IMAP_LOGIN) + conf.put(config.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(config.IMAP_LOGIN) + assert conf.get(config.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From 30428b1af71e57b0401e1e10f387f9873a689307 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 1 Jan 2021 11:18:35 +0100 Subject: [PATCH 291/586] backup sqlite db to json with tinyDB --- stacosys/core/database.py | 24 ++++++++++-------------- tests/test_persistence.py | 20 -------------------- 2 files changed, 10 insertions(+), 34 deletions(-) delete mode 100644 tests/test_persistence.py diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 5be8287..dbce608 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -1,8 +1,10 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import json from playhouse.db_url import connect - +from playhouse.shortcuts import model_to_dict +from tinydb import TinyDB from stacosys.conf import config @@ -15,27 +17,21 @@ def setup(): from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) + if config.exists(config.DB_BACKUP_JSON_FILE): + _backup_db(config.DB_BACKUP_JSON_FILE, Comment) -from playhouse.shortcuts import model_to_dict -import json - - -def tojson_model(comment): +def _tojson_model(comment): dcomment = model_to_dict(comment) - del dcomment["site"] + # del dcomment["site"] tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) return json.loads(tcomment) -def dump_db(): - from tinydb import TinyDB, Query - from stacosys.model.comment import Comment - - db = TinyDB("db.json", sort_keys=True, indent=4, separators=(",", ": ")) +def _backup_db(db_file, Comment): + db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) db.drop_tables() table = db.table("comments") for comment in Comment.select(): - cc = tojson_model(comment) - print(cc) + cc = _tojson_model(comment) table.insert(cc) diff --git a/tests/test_persistence.py b/tests/test_persistence.py deleted file mode 100644 index d4dd92d..0000000 --- a/tests/test_persistence.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import pytest - - -@pytest.fixture -def persistence(): - from stacosys.conf import config - - config.params = {"main.db_file": None} - from stacosys.core import persistence - - return persistence.Persistence() - - -def test_init_persistence(persistence): - assert persistence is not None - assert persistence.get_db() is not None - assert persistence.get_table_comments() is not None From ab54ab981a00478194017ca3f75405efa0f4797a Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 1 Jan 2021 11:18:35 +0100 Subject: [PATCH 292/586] backup sqlite db to json with tinyDB --- stacosys/core/database.py | 24 ++++++++++-------------- tests/test_persistence.py | 20 -------------------- 2 files changed, 10 insertions(+), 34 deletions(-) delete mode 100644 tests/test_persistence.py diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 5be8287..dbce608 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -1,8 +1,10 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import json from playhouse.db_url import connect - +from playhouse.shortcuts import model_to_dict +from tinydb import TinyDB from stacosys.conf import config @@ -15,27 +17,21 @@ def setup(): from stacosys.model.comment import Comment get_db().create_tables([Site, Comment], safe=True) + if config.exists(config.DB_BACKUP_JSON_FILE): + _backup_db(config.DB_BACKUP_JSON_FILE, Comment) -from playhouse.shortcuts import model_to_dict -import json - - -def tojson_model(comment): +def _tojson_model(comment): dcomment = model_to_dict(comment) - del dcomment["site"] + # del dcomment["site"] tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) return json.loads(tcomment) -def dump_db(): - from tinydb import TinyDB, Query - from stacosys.model.comment import Comment - - db = TinyDB("db.json", sort_keys=True, indent=4, separators=(",", ": ")) +def _backup_db(db_file, Comment): + db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) db.drop_tables() table = db.table("comments") for comment in Comment.select(): - cc = tojson_model(comment) - print(cc) + cc = _tojson_model(comment) table.insert(cc) diff --git a/tests/test_persistence.py b/tests/test_persistence.py deleted file mode 100644 index d4dd92d..0000000 --- a/tests/test_persistence.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import pytest - - -@pytest.fixture -def persistence(): - from stacosys.conf import config - - config.params = {"main.db_file": None} - from stacosys.core import persistence - - return persistence.Persistence() - - -def test_init_persistence(persistence): - assert persistence is not None - assert persistence.get_db() is not None - assert persistence.get_table_comments() is not None From 6c855e7ead3c84e3d7755e60ac94d4cdecac268c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 2 Jan 2021 12:36:42 +0100 Subject: [PATCH 293/586] object-oriented and DI --- config.ini | 4 +-- run.py | 67 +++++++++++++++++----------------- stacosys/core/database.py | 35 ++++++++++++------ stacosys/core/rss.py | 73 ++++++++++++++++++++------------------ stacosys/core/templater.py | 6 ++-- stacosys/model/comment.py | 18 ++++------ stacosys/model/site.py | 7 ++-- 7 files changed, 110 insertions(+), 100 deletions(-) diff --git a/config.ini b/config.ini index 8d7b7e2..9a7cfab 100755 --- a/config.ini +++ b/config.ini @@ -2,9 +2,9 @@ ; Default configuration [main] lang = fr -db_url = sqlite:///db.sqlite +db_url = db.sqlite +db_backup_json_file = db.json newcomment_polling = 60 -db_file = db.json [site] name = "My blog" diff --git a/run.py b/run.py index 6fd1a19..5958b8b 100644 --- a/run.py +++ b/run.py @@ -4,12 +4,16 @@ import argparse import logging import os +import sys from flask import Flask from flask_apscheduler import APScheduler -from stacosys.conf import config - +import stacosys.conf.config as config +from stacosys.core import database +from stacosys.core import rss +#from stacosys.interface import api +#from stacosys.interface import form # configure logging def configure_logging(level): @@ -18,7 +22,7 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -29,21 +33,21 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = {'default': {'type': 'threadpool', 'max_workers': 4}} + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} def __init__(self, imap_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { - 'id': 'fetch_mail', - 'func': 'stacosys.core.cron:fetch_mail_answers', - 'trigger': 'interval', - 'seconds': imap_polling_seconds, + "id": "fetch_mail", + "func": "stacosys.core.cron:fetch_mail_answers", + "trigger": "interval", + "seconds": imap_polling_seconds, }, { - 'id': 'submit_new_comment', - 'func': 'stacosys.core.cron:submit_new_comment', - 'trigger': 'interval', - 'seconds': new_comment_polling_seconds, + "id": "submit_new_comment", + "func": "stacosys.core.cron:submit_new_comment", + "trigger": "interval", + "seconds": new_comment_polling_seconds, }, ] @@ -51,56 +55,49 @@ class JobConfig(object): def stacosys_server(config_pathname): app = Flask(__name__) - config.initialize(config_pathname, app) + + conf = config.Config.load(config_pathname) # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) - logging.getLogger('werkzeug').level = logging.WARNING - logging.getLogger('apscheduler.executors').level = logging.WARNING + logging.getLogger("werkzeug").level = logging.WARNING + logging.getLogger("apscheduler.executors").level = logging.WARNING # initialize database - from stacosys.core import database + db = database.Database() + db.setup(conf.get(config.DB_URL)) - database.setup() - database.dump_db() - # cron email fetcher app.config.from_object( JobConfig( - config.get_int(config.IMAP_POLLING), config.get_int(config.COMMENT_POLLING) + conf.get_int(config.IMAP_POLLING), conf.get_int(config.COMMENT_POLLING) ) ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() - logger.info('Start Stacosys application') + logger.info("Start Stacosys application") # generate RSS for all sites - from stacosys.core import rss - - rss.generate_all() + rss_manager = rss.Rss(conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO)) + rss_manager.generate_all() # start Flask - from stacosys.interface import api - - logger.info('Load interface %s' % api) - - from stacosys.interface import form - - logger.info('Load interface %s' % form) + #logger.info("Load interface %s" % api) + #logger.info("Load interface %s" % form) app.run( - host=config.get(config.HTTP_HOST), - port=config.get(config.HTTP_PORT), + host=conf.get(config.HTTP_HOST), + port=conf.get(config.HTTP_PORT), debug=False, use_reloader=False, ) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('config', help='config path name') + parser.add_argument("config", help="config path name") args = parser.parse_args() stacosys_server(args.config) diff --git a/stacosys/core/database.py b/stacosys/core/database.py index dbce608..b70b1dd 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -2,23 +2,37 @@ # -*- coding: UTF-8 -*- import json -from playhouse.db_url import connect +from peewee import DatabaseProxy, Model +from playhouse.db_url import connect, SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB from stacosys.conf import config - -def get_db(): - return connect(config.get(config.DB_URL)) +db = SqliteDatabase(None) -def setup(): - from stacosys.model.site import Site - from stacosys.model.comment import Comment +class BaseModel(Model): + class Meta: + database = db - get_db().create_tables([Site, Comment], safe=True) - if config.exists(config.DB_BACKUP_JSON_FILE): - _backup_db(config.DB_BACKUP_JSON_FILE, Comment) + +class Database: + def get_db(self): + return db + + def setup(self, db_url): + + db.init(db_url) + db.connect() + + from stacosys.model.site import Site + from stacosys.model.comment import Comment + + db.create_tables([Site, Comment], safe=True) + + +# if config.exists(config.DB_BACKUP_JSON_FILE): +# _backup_db(config.DB_BACKUP_JSON_FILE, Comment) def _tojson_model(comment): @@ -35,3 +49,4 @@ def _backup_db(db_file, Comment): for comment in Comment.select(): cc = _tojson_model(comment) table.insert(cc) + diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index a6c3c5d..d333772 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -6,49 +6,54 @@ from datetime import datetime import markdown import PyRSS2Gen -from stacosys.conf import config +import stacosys.conf.config as config from stacosys.core.templater import get_template from stacosys.model.comment import Comment from stacosys.model.site import Site -def generate_all(): +class Rss: + def __init__(self, lang, rss_file, rss_proto): + self._lang = lang + self._rss_file = rss_file + self._rss_proto = rss_proto - for site in Site.select(): - generate_site(site.token) + def generate_all(self): + for site in Site.select(): + self._generate_site(site.token) -def generate_site(token): + def _generate_site(self, token): - site = Site.select().where(Site.token == token).get() - rss_title = get_template('rss_title_message').render(site=site.name) - md = markdown.Markdown() + site = Site.select().where(Site.token == token).get() + rss_title = get_template(self._lang, "rss_title_message").render(site=site.name) + md = markdown.Markdown() - items = [] - for row in ( - Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) - .order_by(-Comment.published) - .limit(10) - ): - item_link = '%s://%s%s' % (config.get(config.RSS_PROTO), site.url, row.url) - items.append( - PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' - % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), - pubDate=row.published, + items = [] + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (self._rss_proto, site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (self._rss_proto, row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) ) - ) - rss = PyRSS2Gen.RSS2( - title=rss_title, - link='%s://%s' % (config.get(config.RSS_PROTO), site.url), - description='Commentaires du site "%s"' % site.name, - lastBuildDate=datetime.now(), - items=items, - ) - rss.write_xml(open(config.get(config.RSS_FILE), 'w'), encoding='utf-8') + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="%s://%s" % (self._rss_proto, site.url), + description='Commentaires du site "%s"' % site.name, + lastBuildDate=datetime.now(), + items=items, + ) + rss.write_xml(open(self._rss_file, "w"), encoding="utf-8") diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index ca7b210..9f2d328 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -8,9 +8,9 @@ from jinja2 import Environment, FileSystemLoader from stacosys.conf import config current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, '../templates')) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) env = Environment(loader=FileSystemLoader(template_path)) -def get_template(name): - return env.get_template(config.get(config.LANG) + '/' + name + '.tpl') +def get_template(lang, name): + return env.get_template(lang + "/" + name + ".tpl") diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 1ebbfad..9493538 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -7,28 +7,24 @@ from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField from stacosys.model.site import Site -from stacosys.core.database import get_db from datetime import datetime +from stacosys.core.database import BaseModel - -class Comment(Model): +class Comment(BaseModel): url = CharField() created = DateTimeField() notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default='') - author_gravatar = CharField(default='') + author_site = CharField(default="") + author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name='site') - - class Meta: - database = get_db() + site = ForeignKeyField(Site, related_name="site") def notify_site_admin(self): - self.notified = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.save() def publish(self): - self.published = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.save() diff --git a/stacosys/model/site.py b/stacosys/model/site.py index bbd6091..b2f4e9b 100644 --- a/stacosys/model/site.py +++ b/stacosys/model/site.py @@ -3,14 +3,11 @@ from peewee import Model from peewee import CharField -from stacosys.core.database import get_db +from stacosys.core.database import BaseModel -class Site(Model): +class Site(BaseModel): name = CharField(unique=True) url = CharField() token = CharField() admin_email = CharField() - - class Meta: - database = get_db() From 962e0d2e412306ac171c936bbb71c63e9bfe8389 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 2 Jan 2021 12:36:42 +0100 Subject: [PATCH 294/586] object-oriented and DI --- config.ini | 4 +-- run.py | 67 +++++++++++++++++----------------- stacosys/core/database.py | 35 ++++++++++++------ stacosys/core/rss.py | 73 ++++++++++++++++++++------------------ stacosys/core/templater.py | 6 ++-- stacosys/model/comment.py | 18 ++++------ stacosys/model/site.py | 7 ++-- 7 files changed, 110 insertions(+), 100 deletions(-) diff --git a/config.ini b/config.ini index 8d7b7e2..9a7cfab 100755 --- a/config.ini +++ b/config.ini @@ -2,9 +2,9 @@ ; Default configuration [main] lang = fr -db_url = sqlite:///db.sqlite +db_url = db.sqlite +db_backup_json_file = db.json newcomment_polling = 60 -db_file = db.json [site] name = "My blog" diff --git a/run.py b/run.py index 6fd1a19..5958b8b 100644 --- a/run.py +++ b/run.py @@ -4,12 +4,16 @@ import argparse import logging import os +import sys from flask import Flask from flask_apscheduler import APScheduler -from stacosys.conf import config - +import stacosys.conf.config as config +from stacosys.core import database +from stacosys.core import rss +#from stacosys.interface import api +#from stacosys.interface import form # configure logging def configure_logging(level): @@ -18,7 +22,7 @@ def configure_logging(level): ch = logging.StreamHandler() ch.setLevel(level) # create formatter - formatter = logging.Formatter('[%(asctime)s] %(name)s %(levelname)s %(message)s') + formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") # add formatter to ch ch.setFormatter(formatter) # add ch to logger @@ -29,21 +33,21 @@ class JobConfig(object): JOBS = [] - SCHEDULER_EXECUTORS = {'default': {'type': 'threadpool', 'max_workers': 4}} + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} def __init__(self, imap_polling_seconds, new_comment_polling_seconds): self.JOBS = [ { - 'id': 'fetch_mail', - 'func': 'stacosys.core.cron:fetch_mail_answers', - 'trigger': 'interval', - 'seconds': imap_polling_seconds, + "id": "fetch_mail", + "func": "stacosys.core.cron:fetch_mail_answers", + "trigger": "interval", + "seconds": imap_polling_seconds, }, { - 'id': 'submit_new_comment', - 'func': 'stacosys.core.cron:submit_new_comment', - 'trigger': 'interval', - 'seconds': new_comment_polling_seconds, + "id": "submit_new_comment", + "func": "stacosys.core.cron:submit_new_comment", + "trigger": "interval", + "seconds": new_comment_polling_seconds, }, ] @@ -51,56 +55,49 @@ class JobConfig(object): def stacosys_server(config_pathname): app = Flask(__name__) - config.initialize(config_pathname, app) + + conf = config.Config.load(config_pathname) # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) - logging.getLogger('werkzeug').level = logging.WARNING - logging.getLogger('apscheduler.executors').level = logging.WARNING + logging.getLogger("werkzeug").level = logging.WARNING + logging.getLogger("apscheduler.executors").level = logging.WARNING # initialize database - from stacosys.core import database + db = database.Database() + db.setup(conf.get(config.DB_URL)) - database.setup() - database.dump_db() - # cron email fetcher app.config.from_object( JobConfig( - config.get_int(config.IMAP_POLLING), config.get_int(config.COMMENT_POLLING) + conf.get_int(config.IMAP_POLLING), conf.get_int(config.COMMENT_POLLING) ) ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() - logger.info('Start Stacosys application') + logger.info("Start Stacosys application") # generate RSS for all sites - from stacosys.core import rss - - rss.generate_all() + rss_manager = rss.Rss(conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO)) + rss_manager.generate_all() # start Flask - from stacosys.interface import api - - logger.info('Load interface %s' % api) - - from stacosys.interface import form - - logger.info('Load interface %s' % form) + #logger.info("Load interface %s" % api) + #logger.info("Load interface %s" % form) app.run( - host=config.get(config.HTTP_HOST), - port=config.get(config.HTTP_PORT), + host=conf.get(config.HTTP_HOST), + port=conf.get(config.HTTP_PORT), debug=False, use_reloader=False, ) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('config', help='config path name') + parser.add_argument("config", help="config path name") args = parser.parse_args() stacosys_server(args.config) diff --git a/stacosys/core/database.py b/stacosys/core/database.py index dbce608..b70b1dd 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -2,23 +2,37 @@ # -*- coding: UTF-8 -*- import json -from playhouse.db_url import connect +from peewee import DatabaseProxy, Model +from playhouse.db_url import connect, SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB from stacosys.conf import config - -def get_db(): - return connect(config.get(config.DB_URL)) +db = SqliteDatabase(None) -def setup(): - from stacosys.model.site import Site - from stacosys.model.comment import Comment +class BaseModel(Model): + class Meta: + database = db - get_db().create_tables([Site, Comment], safe=True) - if config.exists(config.DB_BACKUP_JSON_FILE): - _backup_db(config.DB_BACKUP_JSON_FILE, Comment) + +class Database: + def get_db(self): + return db + + def setup(self, db_url): + + db.init(db_url) + db.connect() + + from stacosys.model.site import Site + from stacosys.model.comment import Comment + + db.create_tables([Site, Comment], safe=True) + + +# if config.exists(config.DB_BACKUP_JSON_FILE): +# _backup_db(config.DB_BACKUP_JSON_FILE, Comment) def _tojson_model(comment): @@ -35,3 +49,4 @@ def _backup_db(db_file, Comment): for comment in Comment.select(): cc = _tojson_model(comment) table.insert(cc) + diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index a6c3c5d..d333772 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -6,49 +6,54 @@ from datetime import datetime import markdown import PyRSS2Gen -from stacosys.conf import config +import stacosys.conf.config as config from stacosys.core.templater import get_template from stacosys.model.comment import Comment from stacosys.model.site import Site -def generate_all(): +class Rss: + def __init__(self, lang, rss_file, rss_proto): + self._lang = lang + self._rss_file = rss_file + self._rss_proto = rss_proto - for site in Site.select(): - generate_site(site.token) + def generate_all(self): + for site in Site.select(): + self._generate_site(site.token) -def generate_site(token): + def _generate_site(self, token): - site = Site.select().where(Site.token == token).get() - rss_title = get_template('rss_title_message').render(site=site.name) - md = markdown.Markdown() + site = Site.select().where(Site.token == token).get() + rss_title = get_template(self._lang, "rss_title_message").render(site=site.name) + md = markdown.Markdown() - items = [] - for row in ( - Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) - .order_by(-Comment.published) - .limit(10) - ): - item_link = '%s://%s%s' % (config.get(config.RSS_PROTO), site.url, row.url) - items.append( - PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' - % (config.get(config.RSS_PROTO), row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), - pubDate=row.published, + items = [] + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (self._rss_proto, site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (self._rss_proto, row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) ) - ) - rss = PyRSS2Gen.RSS2( - title=rss_title, - link='%s://%s' % (config.get(config.RSS_PROTO), site.url), - description='Commentaires du site "%s"' % site.name, - lastBuildDate=datetime.now(), - items=items, - ) - rss.write_xml(open(config.get(config.RSS_FILE), 'w'), encoding='utf-8') + rss = PyRSS2Gen.RSS2( + title=rss_title, + link="%s://%s" % (self._rss_proto, site.url), + description='Commentaires du site "%s"' % site.name, + lastBuildDate=datetime.now(), + items=items, + ) + rss.write_xml(open(self._rss_file, "w"), encoding="utf-8") diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index ca7b210..9f2d328 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -8,9 +8,9 @@ from jinja2 import Environment, FileSystemLoader from stacosys.conf import config current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, '../templates')) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) env = Environment(loader=FileSystemLoader(template_path)) -def get_template(name): - return env.get_template(config.get(config.LANG) + '/' + name + '.tpl') +def get_template(lang, name): + return env.get_template(lang + "/" + name + ".tpl") diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 1ebbfad..9493538 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -7,28 +7,24 @@ from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField from stacosys.model.site import Site -from stacosys.core.database import get_db from datetime import datetime +from stacosys.core.database import BaseModel - -class Comment(Model): +class Comment(BaseModel): url = CharField() created = DateTimeField() notified = DateTimeField(null=True, default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_site = CharField(default='') - author_gravatar = CharField(default='') + author_site = CharField(default="") + author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name='site') - - class Meta: - database = get_db() + site = ForeignKeyField(Site, related_name="site") def notify_site_admin(self): - self.notified = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.save() def publish(self): - self.published = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.save() diff --git a/stacosys/model/site.py b/stacosys/model/site.py index bbd6091..b2f4e9b 100644 --- a/stacosys/model/site.py +++ b/stacosys/model/site.py @@ -3,14 +3,11 @@ from peewee import Model from peewee import CharField -from stacosys.core.database import get_db +from stacosys.core.database import BaseModel -class Site(Model): +class Site(BaseModel): name = CharField(unique=True) url = CharField() token = CharField() admin_email = CharField() - - class Meta: - database = get_db() From adc6451116ca4d31b720e9146061b417694a11f6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Jan 2021 18:24:16 +0100 Subject: [PATCH 295/586] improve encapsulation --- run.py | 83 ++++++++++------------ stacosys/core/cron.py | 117 +++++++++++++++----------------- stacosys/core/mailer.py | 110 ++++++++++++++++++------------ stacosys/interface/__init__.py | 5 ++ stacosys/interface/api.py | 42 ++++++------ stacosys/interface/form.py | 42 ++++++------ stacosys/interface/scheduler.py | 37 ++++++++++ 7 files changed, 240 insertions(+), 196 deletions(-) create mode 100644 stacosys/interface/scheduler.py diff --git a/run.py b/run.py index 5958b8b..efa984d 100644 --- a/run.py +++ b/run.py @@ -1,19 +1,21 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import sys +import os import argparse import logging -import os -import sys - from flask import Flask -from flask_apscheduler import APScheduler import stacosys.conf.config as config from stacosys.core import database -from stacosys.core import rss -#from stacosys.interface import api -#from stacosys.interface import form +from stacosys.core.rss import Rss +from stacosys.core.mailer import Mailer +from stacosys.interface import app +from stacosys.interface import api +from stacosys.interface import form +from stacosys.interface import scheduler + # configure logging def configure_logging(level): @@ -29,33 +31,8 @@ def configure_logging(level): root_logger.addHandler(ch) -class JobConfig(object): - - JOBS = [] - - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - - def __init__(self, imap_polling_seconds, new_comment_polling_seconds): - self.JOBS = [ - { - "id": "fetch_mail", - "func": "stacosys.core.cron:fetch_mail_answers", - "trigger": "interval", - "seconds": imap_polling_seconds, - }, - { - "id": "submit_new_comment", - "func": "stacosys.core.cron:submit_new_comment", - "trigger": "interval", - "seconds": new_comment_polling_seconds, - }, - ] - - def stacosys_server(config_pathname): - app = Flask(__name__) - conf = config.Config.load(config_pathname) # configure logging @@ -68,26 +45,38 @@ def stacosys_server(config_pathname): db = database.Database() db.setup(conf.get(config.DB_URL)) - # cron email fetcher - app.config.from_object( - JobConfig( - conf.get_int(config.IMAP_POLLING), conf.get_int(config.COMMENT_POLLING) - ) - ) - scheduler = APScheduler() - scheduler.init_app(app) - scheduler.start() - logger.info("Start Stacosys application") # generate RSS for all sites - rss_manager = rss.Rss(conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO)) - rss_manager.generate_all() + rss = Rss( + conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO) + ) + rss.generate_all() + + # configure mailer + mailer = Mailer( + conf.get(config.IMAP_HOST), + conf.get_int(config.IMAP_PORT), + conf.get_bool(config.IMAP_SSL), + conf.get(config.IMAP_LOGIN), + conf.get(config.IMAP_PASSWORD), + conf.get(config.SMTP_HOST), + conf.get_int(config.SMTP_PORT), + conf.get_bool(config.SMTP_STARTTLS), + conf.get(config.SMTP_LOGIN), + conf.get(config.SMTP_PASSWORD), + ) + + # configure scheduler + scheduler.configure( + conf.get_int(config.IMAP_POLLING), + conf.get_int(config.COMMENT_POLLING), + conf.get(config.LANG), + mailer, + rss, + ) # start Flask - #logger.info("Load interface %s" % api) - #logger.info("Load interface %s" % form) - app.run( host=conf.get(config.HTTP_HOST), port=conf.get(config.HTTP_PORT), diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 974ab17..95951ee 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -6,7 +6,7 @@ import re import time from datetime import datetime -from stacosys.core import mailer, rss +from stacosys.core import rss from stacosys.core.templater import get_template from stacosys.model.comment import Comment, Site from stacosys.model.email import Email @@ -14,59 +14,18 @@ from stacosys.model.email import Email logger = logging.getLogger(__name__) -def cron(func): - def wrapper(): - logger.debug('execute CRON ' + func.__name__) - func() - - return wrapper - - -@cron -def fetch_mail_answers(): - +def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): - if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): - if _reply_comment_email(msg): + if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): + if _reply_comment_email(lang, mailer, rss, msg): mailer.delete(msg.id) -@cron -def submit_new_comment(): +def _reply_comment_email(lang, mailer, rss, email: Email): - for comment in Comment.select().where(Comment.notified.is_null()): - - comment_list = ( - 'author: %s' % comment.author_name, - 'site: %s' % comment.author_site, - 'date: %s' % comment.created, - 'url: %s' % comment.url, - '', - '%s' % comment.content, - '', - ) - comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render( - url=comment.url, comment=comment_text - ) - - # send email - site = Site.get(Site.id == comment.site) - subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, site.token) - if mailer.send(site.admin_email, subject, email_body): - logger.debug('new comment processed ') - - # notify site admin and save notification datetime - comment.notify_site_admin() - else: - logger.warn('rescheduled. send mail failure ' + subject) - - -def _reply_comment_email(email : Email): - - m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) + m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: - logger.warn('ignore corrupted email. No token %s' % email.subject) + logger.warn("ignore corrupted email. No token %s" % email.subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -75,39 +34,75 @@ def _reply_comment_email(email : Email): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn('unknown comment %d' % comment_id) + logger.warn("unknown comment %d" % comment_id) return True if comment.published: - logger.warn('ignore already published email. token %d' % comment_id) + logger.warn("ignore already published email. token %d" % comment_id) return if comment.site.token != token: - logger.warn('ignore corrupted email. Unknown token %d' % comment_id) + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) return if not email.plain_text_content: - logger.warn('ignore empty email') + logger.warn("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if email.plain_text_content[:2].upper() in ('NO'): - logger.info('discard comment: %d' % comment_id) + if email.plain_text_content[:2].upper() in ("NO"): + logger.info("discard comment: %d" % comment_id) comment.delete_instance() - new_email_body = get_template('drop_comment').render(original=email.plain_text_content) - if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): - logger.warn('minor failure. cannot send rejection mail ' + email.subject) + new_email_body = get_template(lang, "drop_comment").render( + original=email.plain_text_content + ) + if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): + logger.warn("minor failure. cannot send rejection mail " + email.subject) else: # save publishing datetime comment.publish() - logger.info('commit comment: %d' % comment_id) + logger.info("commit comment: %d" % comment_id) # rebuild RSS rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template('approve_comment').render(original=email.plain_text_content) - if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): - logger.warn('minor failure. cannot send approval email ' + email.subject) + new_email_body = get_template(lang, "approve_comment").render( + original=email.plain_text_content + ) + if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): + logger.warn("minor failure. cannot send approval email " + email.subject) return True + + +def submit_new_comment(lang, mailer): + + for comment in Comment.select().where(Comment.notified.is_null()): + + comment_list = ( + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.created, + "url: %s" % comment.url, + "", + "%s" % comment.content, + "", + ) + comment_text = "\n".join(comment_list) + # TODO use constants for template names + email_body = get_template(lang, "new_comment").render( + url=comment.url, comment=comment_text + ) + + # send email + site = Site.get(Site.id == comment.site) + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) + if mailer.send(site.admin_email, subject, email_body): + logger.debug("new comment processed ") + + # notify site admin and save notification datetime + comment.notify_site_admin() + else: + logger.warn("rescheduled. send mail failure " + subject) + diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index bf5fcc3..d4b48c1 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -15,53 +15,75 @@ from stacosys.model.email import Email logger = logging.getLogger(__name__) -def _open_mailbox(): - return imap.Mailbox( - config.get(config.IMAP_HOST), - config.get_int(config.IMAP_PORT), - config.get_bool(config.IMAP_SSL), - config.get(config.IMAP_LOGIN), - config.get(config.IMAP_PASSWORD), - ) +class Mailer: + def __init__( + self, + imap_host, + imap_port, + imap_ssl, + imap_login, + imap_password, + smtp_host, + smtp_port, + smtp_starttls, + smtp_login, + smtp_password, + ): + self._imap_host = imap_host + self._imap_port = imap_port + self._imap_ssl = imap_ssl + self._imap_login = imap_login + self._imap_password = imap_password + self._smtp_host = smtp_host + self._smtp_port = smtp_port + self._smtp_starttls = smtp_starttls + self._smtp_login = smtp_login + self._smtp_password = smtp_password + def _open_mailbox(self): + return imap.Mailbox( + self._imap_host, + self._imap_port, + self._imap_ssl, + self._imap_login, + self._imap_password, + ) -def fetch(): - msgs = [] - try: - with _open_mailbox() as mbox: - count = mbox.get_count() - for num in range(count): - msgs.append(mbox.fetch_message(num + 1)) - except: - logger.exception("fetch mail exception") - return msgs + def fetch(self): + msgs = [] + try: + with self._open_mailbox() as mbox: + count = mbox.get_count() + for num in range(count): + msgs.append(mbox.fetch_message(num + 1)) + except: + logger.exception("fetch mail exception") + return msgs + def send(self, to_email, subject, message): -def send(to_email, subject, message): + # Create the container (outer) email message. + msg = MIMEText(message) + msg["Subject"] = subject + msg["To"] = to_email + msg["From"] = self._smtp_login - # Create the container (outer) email message. - msg = MIMEText(message) - msg["Subject"] = subject - msg["To"] = to_email - msg["From"] = config.get(config.SMTP_LOGIN) + success = True + try: + s = smtplib.SMTP(self._smtp_host, self._smtp_port) + if self._smtp_starttls: + s.starttls() + s.login(self._smtp_login, self._smtp_password) + s.send_message(msg) + s.quit() + except: + logger.exception("send mail exception") + success = False + return success - success = True - try: - s = smtplib.SMTP(config.get(config.SMTP_HOST), config.get_int(config.SMTP_PORT)) - if config.get_bool(config.SMTP_STARTTLS): - s.starttls() - s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) - s.send_message(msg) - s.quit() - except: - logger.exception("send mail exception") - success = False - return success - - -def delete(id): - try: - with _open_mailbox() as mbox: - mbox.delete_message(id) - except: - logger.exception("delete mail exception") + def delete(self, id): + try: + with self._open_mailbox() as mbox: + mbox.delete_message(id) + except: + logger.exception("delete mail exception") diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index e69de29..b7714c3 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from flask import Flask +app = Flask(__name__) \ No newline at end of file diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index dcc63c1..90d192a 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -2,31 +2,29 @@ # -*- coding: utf-8 -*- import logging - from flask import abort, jsonify, request -from stacosys.conf import config +from stacosys.interface import app from stacosys.model.comment import Comment from stacosys.model.site import Site logger = logging.getLogger(__name__) -app = config.flaskapp() -@app.route('/ping', methods=['GET']) +@app.route("/ping", methods=["GET"]) def ping(): - return 'OK' + return "OK" -@app.route('/comments', methods=['GET']) +@app.route("/comments", methods=["GET"]) def query_comments(): comments = [] try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") - logger.info('retrieve comments for url %s' % (url)) + logger.info("retrieve comments for url %s" % (url)) for comment in ( Comment.select(Comment) .join(Site) @@ -38,29 +36,29 @@ def query_comments(): .order_by(+Comment.published) ): d = { - 'author': comment.author_name, - 'content': comment.content, - 'avatar': comment.author_gravatar, - 'date': comment.published.strftime('%Y-%m-%d %H:%M:%S') + "author": comment.author_name, + "content": comment.content, + "avatar": comment.author_gravatar, + "date": comment.published.strftime("%Y-%m-%d %H:%M:%S"), } if comment.author_site: - d['site'] = comment.author_site + d["site"] = comment.author_site logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) + r = jsonify({"data": comments}) r.status_code = 200 except: - logger.warn('bad request') - r = jsonify({'data': []}) + logger.warn("bad request") + r = jsonify({"data": []}) r.status_code = 400 return r -@app.route('/comments/count', methods=['GET']) +@app.route("/comments/count", methods=["GET"]) def get_comments_count(): try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") count = ( Comment.select(Comment) .join(Site) @@ -71,9 +69,9 @@ def get_comments_count(): ) .count() ) - r = jsonify({'count': count}) + r = jsonify({"count": count}) r.status_code = 200 except: - r = jsonify({'count': 0}) + r = jsonify({"count": 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 738a958..df0684c 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -3,53 +3,51 @@ import logging from datetime import datetime - from flask import abort, redirect, request -from stacosys.conf import config +from stacosys.interface import app from stacosys.model.comment import Comment from stacosys.model.site import Site logger = logging.getLogger(__name__) -app = config.flaskapp() -@app.route('/newcomment', methods=['POST']) +@app.route("/newcomment", methods=["POST"]) def new_form_comment(): try: data = request.form - logger.info('form data ' + str(data)) + logger.info("form data " + str(data)) # validate token: retrieve site entity - token = data.get('token', '') + token = data.get("token", "") site = Site.select().where(Site.token == token).get() if site is None: - logger.warn('Unknown site %s' % token) + logger.warn("Unknown site %s" % token) abort(400) # honeypot for spammers - captcha = data.get('remarque', '') + captcha = data.get("remarque", "") if captcha: - logger.warn('discard spam: data %s' % data) + logger.warn("discard spam: data %s" % data) abort(400) - url = data.get('url', '') - author_name = data.get('author', '').strip() - author_gravatar = data.get('email', '').strip() - author_site = data.get('site', '').lower().strip() - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site - message = data.get('message', '') + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") # anti-spam again if not url or not author_name or not message: - logger.warn('empty field: data %s' % data) + logger.warn("empty field: data %s" % data) abort(400) check_form_data(data) # add a row to Comment table - created = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( site=site, url=url, @@ -64,18 +62,18 @@ def new_form_comment(): comment.save() except: - logger.exception('new comment failure') + logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) + return redirect("/redirect/", code=302) def check_form_data(data): - fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + fields = ["url", "message", "site", "remarque", "author", "token", "email"] d = data.to_dict() for field in fields: if field in d: del d[field] if d: - logger.warn('additional field: data %s' % data) + logger.warn("additional field: data %s" % data) abort(400) diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py new file mode 100644 index 0000000..0127282 --- /dev/null +++ b/stacosys/interface/scheduler.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from flask_apscheduler import APScheduler +from stacosys.interface import app + + +class JobConfig(object): + + JOBS = [] + + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} + + def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, mailer, rss): + self.JOBS = [ + { + "id": "fetch_mail", + "func": "stacosys.core.cron:fetch_mail_answers", + "args": [lang, mailer, rss], + "trigger": "interval", + "seconds": imap_polling_seconds, + }, + { + "id": "submit_new_comment", + "func": "stacosys.core.cron:submit_new_comment", + "args": [lang, mailer], + "trigger": "interval", + "seconds": new_comment_polling_seconds, + }, + ] + + +def configure(imap_polling, comment_polling, lang, mailer, rss): + app.config.from_object(JobConfig(imap_polling, comment_polling, lang, mailer, rss)) + scheduler = APScheduler() + scheduler.init_app(app) + scheduler.start() From 5fea1e358ac32089979232ab487bf28e7075a635 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Jan 2021 18:24:16 +0100 Subject: [PATCH 296/586] improve encapsulation --- run.py | 83 ++++++++++------------ stacosys/core/cron.py | 117 +++++++++++++++----------------- stacosys/core/mailer.py | 110 ++++++++++++++++++------------ stacosys/interface/__init__.py | 5 ++ stacosys/interface/api.py | 42 ++++++------ stacosys/interface/form.py | 42 ++++++------ stacosys/interface/scheduler.py | 37 ++++++++++ 7 files changed, 240 insertions(+), 196 deletions(-) create mode 100644 stacosys/interface/scheduler.py diff --git a/run.py b/run.py index 5958b8b..efa984d 100644 --- a/run.py +++ b/run.py @@ -1,19 +1,21 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import sys +import os import argparse import logging -import os -import sys - from flask import Flask -from flask_apscheduler import APScheduler import stacosys.conf.config as config from stacosys.core import database -from stacosys.core import rss -#from stacosys.interface import api -#from stacosys.interface import form +from stacosys.core.rss import Rss +from stacosys.core.mailer import Mailer +from stacosys.interface import app +from stacosys.interface import api +from stacosys.interface import form +from stacosys.interface import scheduler + # configure logging def configure_logging(level): @@ -29,33 +31,8 @@ def configure_logging(level): root_logger.addHandler(ch) -class JobConfig(object): - - JOBS = [] - - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - - def __init__(self, imap_polling_seconds, new_comment_polling_seconds): - self.JOBS = [ - { - "id": "fetch_mail", - "func": "stacosys.core.cron:fetch_mail_answers", - "trigger": "interval", - "seconds": imap_polling_seconds, - }, - { - "id": "submit_new_comment", - "func": "stacosys.core.cron:submit_new_comment", - "trigger": "interval", - "seconds": new_comment_polling_seconds, - }, - ] - - def stacosys_server(config_pathname): - app = Flask(__name__) - conf = config.Config.load(config_pathname) # configure logging @@ -68,26 +45,38 @@ def stacosys_server(config_pathname): db = database.Database() db.setup(conf.get(config.DB_URL)) - # cron email fetcher - app.config.from_object( - JobConfig( - conf.get_int(config.IMAP_POLLING), conf.get_int(config.COMMENT_POLLING) - ) - ) - scheduler = APScheduler() - scheduler.init_app(app) - scheduler.start() - logger.info("Start Stacosys application") # generate RSS for all sites - rss_manager = rss.Rss(conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO)) - rss_manager.generate_all() + rss = Rss( + conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO) + ) + rss.generate_all() + + # configure mailer + mailer = Mailer( + conf.get(config.IMAP_HOST), + conf.get_int(config.IMAP_PORT), + conf.get_bool(config.IMAP_SSL), + conf.get(config.IMAP_LOGIN), + conf.get(config.IMAP_PASSWORD), + conf.get(config.SMTP_HOST), + conf.get_int(config.SMTP_PORT), + conf.get_bool(config.SMTP_STARTTLS), + conf.get(config.SMTP_LOGIN), + conf.get(config.SMTP_PASSWORD), + ) + + # configure scheduler + scheduler.configure( + conf.get_int(config.IMAP_POLLING), + conf.get_int(config.COMMENT_POLLING), + conf.get(config.LANG), + mailer, + rss, + ) # start Flask - #logger.info("Load interface %s" % api) - #logger.info("Load interface %s" % form) - app.run( host=conf.get(config.HTTP_HOST), port=conf.get(config.HTTP_PORT), diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 974ab17..95951ee 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -6,7 +6,7 @@ import re import time from datetime import datetime -from stacosys.core import mailer, rss +from stacosys.core import rss from stacosys.core.templater import get_template from stacosys.model.comment import Comment, Site from stacosys.model.email import Email @@ -14,59 +14,18 @@ from stacosys.model.email import Email logger = logging.getLogger(__name__) -def cron(func): - def wrapper(): - logger.debug('execute CRON ' + func.__name__) - func() - - return wrapper - - -@cron -def fetch_mail_answers(): - +def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): - if re.search(r'.*STACOSYS.*\[(\d+)\:(\w+)\]', msg.subject, re.DOTALL): - if _reply_comment_email(msg): + if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): + if _reply_comment_email(lang, mailer, rss, msg): mailer.delete(msg.id) -@cron -def submit_new_comment(): +def _reply_comment_email(lang, mailer, rss, email: Email): - for comment in Comment.select().where(Comment.notified.is_null()): - - comment_list = ( - 'author: %s' % comment.author_name, - 'site: %s' % comment.author_site, - 'date: %s' % comment.created, - 'url: %s' % comment.url, - '', - '%s' % comment.content, - '', - ) - comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render( - url=comment.url, comment=comment_text - ) - - # send email - site = Site.get(Site.id == comment.site) - subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, site.token) - if mailer.send(site.admin_email, subject, email_body): - logger.debug('new comment processed ') - - # notify site admin and save notification datetime - comment.notify_site_admin() - else: - logger.warn('rescheduled. send mail failure ' + subject) - - -def _reply_comment_email(email : Email): - - m = re.search(r'\[(\d+)\:(\w+)\]', email.subject) + m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: - logger.warn('ignore corrupted email. No token %s' % email.subject) + logger.warn("ignore corrupted email. No token %s" % email.subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -75,39 +34,75 @@ def _reply_comment_email(email : Email): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn('unknown comment %d' % comment_id) + logger.warn("unknown comment %d" % comment_id) return True if comment.published: - logger.warn('ignore already published email. token %d' % comment_id) + logger.warn("ignore already published email. token %d" % comment_id) return if comment.site.token != token: - logger.warn('ignore corrupted email. Unknown token %d' % comment_id) + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) return if not email.plain_text_content: - logger.warn('ignore empty email') + logger.warn("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if email.plain_text_content[:2].upper() in ('NO'): - logger.info('discard comment: %d' % comment_id) + if email.plain_text_content[:2].upper() in ("NO"): + logger.info("discard comment: %d" % comment_id) comment.delete_instance() - new_email_body = get_template('drop_comment').render(original=email.plain_text_content) - if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): - logger.warn('minor failure. cannot send rejection mail ' + email.subject) + new_email_body = get_template(lang, "drop_comment").render( + original=email.plain_text_content + ) + if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): + logger.warn("minor failure. cannot send rejection mail " + email.subject) else: # save publishing datetime comment.publish() - logger.info('commit comment: %d' % comment_id) + logger.info("commit comment: %d" % comment_id) # rebuild RSS rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template('approve_comment').render(original=email.plain_text_content) - if not mailer.send(email.from_addr, 'Re: ' + email.subject, new_email_body): - logger.warn('minor failure. cannot send approval email ' + email.subject) + new_email_body = get_template(lang, "approve_comment").render( + original=email.plain_text_content + ) + if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): + logger.warn("minor failure. cannot send approval email " + email.subject) return True + + +def submit_new_comment(lang, mailer): + + for comment in Comment.select().where(Comment.notified.is_null()): + + comment_list = ( + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.created, + "url: %s" % comment.url, + "", + "%s" % comment.content, + "", + ) + comment_text = "\n".join(comment_list) + # TODO use constants for template names + email_body = get_template(lang, "new_comment").render( + url=comment.url, comment=comment_text + ) + + # send email + site = Site.get(Site.id == comment.site) + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) + if mailer.send(site.admin_email, subject, email_body): + logger.debug("new comment processed ") + + # notify site admin and save notification datetime + comment.notify_site_admin() + else: + logger.warn("rescheduled. send mail failure " + subject) + diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index bf5fcc3..d4b48c1 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -15,53 +15,75 @@ from stacosys.model.email import Email logger = logging.getLogger(__name__) -def _open_mailbox(): - return imap.Mailbox( - config.get(config.IMAP_HOST), - config.get_int(config.IMAP_PORT), - config.get_bool(config.IMAP_SSL), - config.get(config.IMAP_LOGIN), - config.get(config.IMAP_PASSWORD), - ) +class Mailer: + def __init__( + self, + imap_host, + imap_port, + imap_ssl, + imap_login, + imap_password, + smtp_host, + smtp_port, + smtp_starttls, + smtp_login, + smtp_password, + ): + self._imap_host = imap_host + self._imap_port = imap_port + self._imap_ssl = imap_ssl + self._imap_login = imap_login + self._imap_password = imap_password + self._smtp_host = smtp_host + self._smtp_port = smtp_port + self._smtp_starttls = smtp_starttls + self._smtp_login = smtp_login + self._smtp_password = smtp_password + def _open_mailbox(self): + return imap.Mailbox( + self._imap_host, + self._imap_port, + self._imap_ssl, + self._imap_login, + self._imap_password, + ) -def fetch(): - msgs = [] - try: - with _open_mailbox() as mbox: - count = mbox.get_count() - for num in range(count): - msgs.append(mbox.fetch_message(num + 1)) - except: - logger.exception("fetch mail exception") - return msgs + def fetch(self): + msgs = [] + try: + with self._open_mailbox() as mbox: + count = mbox.get_count() + for num in range(count): + msgs.append(mbox.fetch_message(num + 1)) + except: + logger.exception("fetch mail exception") + return msgs + def send(self, to_email, subject, message): -def send(to_email, subject, message): + # Create the container (outer) email message. + msg = MIMEText(message) + msg["Subject"] = subject + msg["To"] = to_email + msg["From"] = self._smtp_login - # Create the container (outer) email message. - msg = MIMEText(message) - msg["Subject"] = subject - msg["To"] = to_email - msg["From"] = config.get(config.SMTP_LOGIN) + success = True + try: + s = smtplib.SMTP(self._smtp_host, self._smtp_port) + if self._smtp_starttls: + s.starttls() + s.login(self._smtp_login, self._smtp_password) + s.send_message(msg) + s.quit() + except: + logger.exception("send mail exception") + success = False + return success - success = True - try: - s = smtplib.SMTP(config.get(config.SMTP_HOST), config.get_int(config.SMTP_PORT)) - if config.get_bool(config.SMTP_STARTTLS): - s.starttls() - s.login(config.get(config.SMTP_LOGIN), config.get(config.SMTP_PASSWORD)) - s.send_message(msg) - s.quit() - except: - logger.exception("send mail exception") - success = False - return success - - -def delete(id): - try: - with _open_mailbox() as mbox: - mbox.delete_message(id) - except: - logger.exception("delete mail exception") + def delete(self, id): + try: + with self._open_mailbox() as mbox: + mbox.delete_message(id) + except: + logger.exception("delete mail exception") diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index e69de29..b7714c3 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from flask import Flask +app = Flask(__name__) \ No newline at end of file diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index dcc63c1..90d192a 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -2,31 +2,29 @@ # -*- coding: utf-8 -*- import logging - from flask import abort, jsonify, request -from stacosys.conf import config +from stacosys.interface import app from stacosys.model.comment import Comment from stacosys.model.site import Site logger = logging.getLogger(__name__) -app = config.flaskapp() -@app.route('/ping', methods=['GET']) +@app.route("/ping", methods=["GET"]) def ping(): - return 'OK' + return "OK" -@app.route('/comments', methods=['GET']) +@app.route("/comments", methods=["GET"]) def query_comments(): comments = [] try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") - logger.info('retrieve comments for url %s' % (url)) + logger.info("retrieve comments for url %s" % (url)) for comment in ( Comment.select(Comment) .join(Site) @@ -38,29 +36,29 @@ def query_comments(): .order_by(+Comment.published) ): d = { - 'author': comment.author_name, - 'content': comment.content, - 'avatar': comment.author_gravatar, - 'date': comment.published.strftime('%Y-%m-%d %H:%M:%S') + "author": comment.author_name, + "content": comment.content, + "avatar": comment.author_gravatar, + "date": comment.published.strftime("%Y-%m-%d %H:%M:%S"), } if comment.author_site: - d['site'] = comment.author_site + d["site"] = comment.author_site logger.debug(d) comments.append(d) - r = jsonify({'data': comments}) + r = jsonify({"data": comments}) r.status_code = 200 except: - logger.warn('bad request') - r = jsonify({'data': []}) + logger.warn("bad request") + r = jsonify({"data": []}) r.status_code = 400 return r -@app.route('/comments/count', methods=['GET']) +@app.route("/comments/count", methods=["GET"]) def get_comments_count(): try: - token = request.args.get('token', '') - url = request.args.get('url', '') + token = request.args.get("token", "") + url = request.args.get("url", "") count = ( Comment.select(Comment) .join(Site) @@ -71,9 +69,9 @@ def get_comments_count(): ) .count() ) - r = jsonify({'count': count}) + r = jsonify({"count": count}) r.status_code = 200 except: - r = jsonify({'count': 0}) + r = jsonify({"count": 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 738a958..df0684c 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -3,53 +3,51 @@ import logging from datetime import datetime - from flask import abort, redirect, request -from stacosys.conf import config +from stacosys.interface import app from stacosys.model.comment import Comment from stacosys.model.site import Site logger = logging.getLogger(__name__) -app = config.flaskapp() -@app.route('/newcomment', methods=['POST']) +@app.route("/newcomment", methods=["POST"]) def new_form_comment(): try: data = request.form - logger.info('form data ' + str(data)) + logger.info("form data " + str(data)) # validate token: retrieve site entity - token = data.get('token', '') + token = data.get("token", "") site = Site.select().where(Site.token == token).get() if site is None: - logger.warn('Unknown site %s' % token) + logger.warn("Unknown site %s" % token) abort(400) # honeypot for spammers - captcha = data.get('remarque', '') + captcha = data.get("remarque", "") if captcha: - logger.warn('discard spam: data %s' % data) + logger.warn("discard spam: data %s" % data) abort(400) - url = data.get('url', '') - author_name = data.get('author', '').strip() - author_gravatar = data.get('email', '').strip() - author_site = data.get('site', '').lower().strip() - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site - message = data.get('message', '') + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") # anti-spam again if not url or not author_name or not message: - logger.warn('empty field: data %s' % data) + logger.warn("empty field: data %s" % data) abort(400) check_form_data(data) # add a row to Comment table - created = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( site=site, url=url, @@ -64,18 +62,18 @@ def new_form_comment(): comment.save() except: - logger.exception('new comment failure') + logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) + return redirect("/redirect/", code=302) def check_form_data(data): - fields = ['url', 'message', 'site', 'remarque', 'author', 'token', 'email'] + fields = ["url", "message", "site", "remarque", "author", "token", "email"] d = data.to_dict() for field in fields: if field in d: del d[field] if d: - logger.warn('additional field: data %s' % data) + logger.warn("additional field: data %s" % data) abort(400) diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py new file mode 100644 index 0000000..0127282 --- /dev/null +++ b/stacosys/interface/scheduler.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from flask_apscheduler import APScheduler +from stacosys.interface import app + + +class JobConfig(object): + + JOBS = [] + + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} + + def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, mailer, rss): + self.JOBS = [ + { + "id": "fetch_mail", + "func": "stacosys.core.cron:fetch_mail_answers", + "args": [lang, mailer, rss], + "trigger": "interval", + "seconds": imap_polling_seconds, + }, + { + "id": "submit_new_comment", + "func": "stacosys.core.cron:submit_new_comment", + "args": [lang, mailer], + "trigger": "interval", + "seconds": new_comment_polling_seconds, + }, + ] + + +def configure(imap_polling, comment_polling, lang, mailer, rss): + app.config.from_object(JobConfig(imap_polling, comment_polling, lang, mailer, rss)) + scheduler = APScheduler() + scheduler.init_app(app) + scheduler.start() From b72d495cece16a66c89a5b10a42b3e2836e1fa35 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Jan 2021 18:24:34 +0100 Subject: [PATCH 297/586] no more used --- stacosys/core/persistence.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 stacosys/core/persistence.py diff --git a/stacosys/core/persistence.py b/stacosys/core/persistence.py deleted file mode 100644 index 32351dd..0000000 --- a/stacosys/core/persistence.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from tinydb import TinyDB -from tinydb.storages import MemoryStorage - -from stacosys.conf import config - - -class Persistence: - def __init__(self): - db_file = config.get(config.DB_FILE) - if db_file: - self.db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) - else: - self.db = TinyDB(storage=MemoryStorage) - - def get_db(self): - return self.db - - def get_table_comments(self): - return self.db.table("comments") From b0867f0e44f543e94388a3290e5da6e92e568f79 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Jan 2021 18:24:34 +0100 Subject: [PATCH 298/586] no more used --- stacosys/core/persistence.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 stacosys/core/persistence.py diff --git a/stacosys/core/persistence.py b/stacosys/core/persistence.py deleted file mode 100644 index 32351dd..0000000 --- a/stacosys/core/persistence.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from tinydb import TinyDB -from tinydb.storages import MemoryStorage - -from stacosys.conf import config - - -class Persistence: - def __init__(self): - db_file = config.get(config.DB_FILE) - if db_file: - self.db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) - else: - self.db = TinyDB(storage=MemoryStorage) - - def get_db(self): - return self.db - - def get_table_comments(self): - return self.db.table("comments") From 8b38769cfce3e59175eaf3e78f7fbb6d789b2804 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Jan 2021 18:25:19 +0100 Subject: [PATCH 299/586] unit test templater --- stacosys/core/templater.py | 15 ++++++++++++++- tests/test_templater.py | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/test_templater.py diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 9f2d328..d0bac1b 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -5,12 +5,25 @@ import os from jinja2 import Environment, FileSystemLoader -from stacosys.conf import config current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) env = Environment(loader=FileSystemLoader(template_path)) +TEMPLATE_DROP_COMMENT = "drop_comment" +TEMPLATE_APPROVE_COMMENT = "approve_comment" +TEMPLATE_NEW_COMMENT = "new_comment" def get_template(lang, name): return env.get_template(lang + "/" + name + ".tpl") + +class Templater: + + def __init__(self, lang, template_path): + self._env = Environment(loader=FileSystemLoader(template_path)) + self._lang = lang + + def get_template(self, name): + return self._env.get_template(self._lang + "/" + name + ".tpl") + + diff --git a/tests/test_templater.py b/tests/test_templater.py new file mode 100644 index 0000000..5b58e3e --- /dev/null +++ b/tests/test_templater.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import os +import pytest + +from stacosys.core.templater import Templater, TEMPLATE_APPROVE_COMMENT + +@pytest.fixture +def templater_fr(): + current_path = os.path.dirname(__file__) + template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) + return Templater("fr", template_path) + +def test_approve_comment_fr(templater_fr): + template = templater_fr.get_template(TEMPLATE_APPROVE_COMMENT) + assert template + content = template.render(original="texte original") + assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") + assert content.endswith("texte original") From 23a45580eee02053837fa75801ad1b21099d6fd9 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 3 Jan 2021 18:25:19 +0100 Subject: [PATCH 300/586] unit test templater --- stacosys/core/templater.py | 15 ++++++++++++++- tests/test_templater.py | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/test_templater.py diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 9f2d328..d0bac1b 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -5,12 +5,25 @@ import os from jinja2 import Environment, FileSystemLoader -from stacosys.conf import config current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) env = Environment(loader=FileSystemLoader(template_path)) +TEMPLATE_DROP_COMMENT = "drop_comment" +TEMPLATE_APPROVE_COMMENT = "approve_comment" +TEMPLATE_NEW_COMMENT = "new_comment" def get_template(lang, name): return env.get_template(lang + "/" + name + ".tpl") + +class Templater: + + def __init__(self, lang, template_path): + self._env = Environment(loader=FileSystemLoader(template_path)) + self._lang = lang + + def get_template(self, name): + return self._env.get_template(self._lang + "/" + name + ".tpl") + + diff --git a/tests/test_templater.py b/tests/test_templater.py new file mode 100644 index 0000000..5b58e3e --- /dev/null +++ b/tests/test_templater.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import os +import pytest + +from stacosys.core.templater import Templater, TEMPLATE_APPROVE_COMMENT + +@pytest.fixture +def templater_fr(): + current_path = os.path.dirname(__file__) + template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) + return Templater("fr", template_path) + +def test_approve_comment_fr(templater_fr): + template = templater_fr.get_template(TEMPLATE_APPROVE_COMMENT) + assert template + content = template.render(original="texte original") + assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") + assert content.endswith("texte original") From 6397e547d8ecf2a049fb09ebc68a20d0935f5d37 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 9 Jan 2021 19:15:58 +0100 Subject: [PATCH 301/586] pytest templater --- stacosys/core/templater.py | 6 ++-- tests/test_templater.py | 62 ++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index d0bac1b..6ab8511 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -13,12 +13,15 @@ env = Environment(loader=FileSystemLoader(template_path)) TEMPLATE_DROP_COMMENT = "drop_comment" TEMPLATE_APPROVE_COMMENT = "approve_comment" TEMPLATE_NEW_COMMENT = "new_comment" +TEMPLATE_NOTIFY_MESSAGE = "notify_message" +TEMPLATE_RSS_TITLE_MESSAGE = "rss_title_message" + def get_template(lang, name): return env.get_template(lang + "/" + name + ".tpl") -class Templater: +class Templater: def __init__(self, lang, template_path): self._env = Environment(loader=FileSystemLoader(template_path)) self._lang = lang @@ -26,4 +29,3 @@ class Templater: def get_template(self, name): return self._env.get_template(self._lang + "/" + name + ".tpl") - diff --git a/tests/test_templater.py b/tests/test_templater.py index 5b58e3e..2d23ba0 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -4,17 +4,61 @@ import os import pytest -from stacosys.core.templater import Templater, TEMPLATE_APPROVE_COMMENT +from stacosys.core.templater import ( + Templater, + TEMPLATE_APPROVE_COMMENT, + TEMPLATE_DROP_COMMENT, + TEMPLATE_NEW_COMMENT, + TEMPLATE_NOTIFY_MESSAGE, + TEMPLATE_RSS_TITLE_MESSAGE, +) -@pytest.fixture -def templater_fr(): + +def get_template_content(lang, template_name, **kwargs): current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - return Templater("fr", template_path) - -def test_approve_comment_fr(templater_fr): - template = templater_fr.get_template(TEMPLATE_APPROVE_COMMENT) + templater = Templater(lang, template_path) + template = templater.get_template(template_name) assert template - content = template.render(original="texte original") + return template.render(kwargs) + + +def test_approve_comment(): + content = get_template_content("fr", TEMPLATE_APPROVE_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") - assert content.endswith("texte original") + assert content.endswith("[texte]") + content = get_template_content("en", TEMPLATE_APPROVE_COMMENT, original="[texte]") + assert content.startswith("Hi,\n\nThe comment should be published soon.") + assert content.endswith("[texte]") + + +def test_drop_comment(): + content = get_template_content("fr", TEMPLATE_DROP_COMMENT, original="[texte]") + assert content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.") + assert content.endswith("[texte]") + content = get_template_content("en", TEMPLATE_DROP_COMMENT, original="[texte]") + assert content.startswith("Hi,\n\nThe comment will not be published.") + assert content.endswith("[texte]") + + +def test_new_comment(): + content = get_template_content("fr", TEMPLATE_NEW_COMMENT, comment="[comment]") + assert content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté") + assert content.endswith("[comment]\n\n--\nStacosys") + content = get_template_content("en", TEMPLATE_NEW_COMMENT, comment="[comment]") + assert content.startswith("Hi,\n\nA new comment has been submitted") + assert content.endswith("[comment]\n\n--\nStacosys") + + +def test_notify_message(): + content = get_template_content("fr", TEMPLATE_NOTIFY_MESSAGE) + assert content == "Nouveau commentaire" + content = get_template_content("en", TEMPLATE_NOTIFY_MESSAGE) + assert content == "New comment" + + +def test_rss_title(): + content = get_template_content("fr", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + assert content == "[site] : commentaires" + content = get_template_content("en", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + assert content == "[site] : comments" From fde8c33de030d5fa065db6a3cd17d6e25dd95e7b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 9 Jan 2021 19:15:58 +0100 Subject: [PATCH 302/586] pytest templater --- stacosys/core/templater.py | 6 ++-- tests/test_templater.py | 62 ++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index d0bac1b..6ab8511 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -13,12 +13,15 @@ env = Environment(loader=FileSystemLoader(template_path)) TEMPLATE_DROP_COMMENT = "drop_comment" TEMPLATE_APPROVE_COMMENT = "approve_comment" TEMPLATE_NEW_COMMENT = "new_comment" +TEMPLATE_NOTIFY_MESSAGE = "notify_message" +TEMPLATE_RSS_TITLE_MESSAGE = "rss_title_message" + def get_template(lang, name): return env.get_template(lang + "/" + name + ".tpl") -class Templater: +class Templater: def __init__(self, lang, template_path): self._env = Environment(loader=FileSystemLoader(template_path)) self._lang = lang @@ -26,4 +29,3 @@ class Templater: def get_template(self, name): return self._env.get_template(self._lang + "/" + name + ".tpl") - diff --git a/tests/test_templater.py b/tests/test_templater.py index 5b58e3e..2d23ba0 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -4,17 +4,61 @@ import os import pytest -from stacosys.core.templater import Templater, TEMPLATE_APPROVE_COMMENT +from stacosys.core.templater import ( + Templater, + TEMPLATE_APPROVE_COMMENT, + TEMPLATE_DROP_COMMENT, + TEMPLATE_NEW_COMMENT, + TEMPLATE_NOTIFY_MESSAGE, + TEMPLATE_RSS_TITLE_MESSAGE, +) -@pytest.fixture -def templater_fr(): + +def get_template_content(lang, template_name, **kwargs): current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - return Templater("fr", template_path) - -def test_approve_comment_fr(templater_fr): - template = templater_fr.get_template(TEMPLATE_APPROVE_COMMENT) + templater = Templater(lang, template_path) + template = templater.get_template(template_name) assert template - content = template.render(original="texte original") + return template.render(kwargs) + + +def test_approve_comment(): + content = get_template_content("fr", TEMPLATE_APPROVE_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") - assert content.endswith("texte original") + assert content.endswith("[texte]") + content = get_template_content("en", TEMPLATE_APPROVE_COMMENT, original="[texte]") + assert content.startswith("Hi,\n\nThe comment should be published soon.") + assert content.endswith("[texte]") + + +def test_drop_comment(): + content = get_template_content("fr", TEMPLATE_DROP_COMMENT, original="[texte]") + assert content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.") + assert content.endswith("[texte]") + content = get_template_content("en", TEMPLATE_DROP_COMMENT, original="[texte]") + assert content.startswith("Hi,\n\nThe comment will not be published.") + assert content.endswith("[texte]") + + +def test_new_comment(): + content = get_template_content("fr", TEMPLATE_NEW_COMMENT, comment="[comment]") + assert content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté") + assert content.endswith("[comment]\n\n--\nStacosys") + content = get_template_content("en", TEMPLATE_NEW_COMMENT, comment="[comment]") + assert content.startswith("Hi,\n\nA new comment has been submitted") + assert content.endswith("[comment]\n\n--\nStacosys") + + +def test_notify_message(): + content = get_template_content("fr", TEMPLATE_NOTIFY_MESSAGE) + assert content == "Nouveau commentaire" + content = get_template_content("en", TEMPLATE_NOTIFY_MESSAGE) + assert content == "New comment" + + +def test_rss_title(): + content = get_template_content("fr", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + assert content == "[site] : commentaires" + content = get_template_content("en", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + assert content == "[site] : comments" From 9c39cf6d3036fef240cc17200bce1a38aa1cca07 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 10 Jan 2021 14:26:59 +0100 Subject: [PATCH 303/586] connect new templater implementation --- stacosys/core/cron.py | 13 ++++++++----- stacosys/core/rss.py | 10 ++++++++-- stacosys/core/templater.py | 31 +++++++++++-------------------- tests/test_templater.py | 33 +++++++++++++-------------------- 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 95951ee..920dfc3 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -2,17 +2,21 @@ # -*- coding: utf-8 -*- import logging +import os import re import time from datetime import datetime from stacosys.core import rss -from stacosys.core.templater import get_template +from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment, Site from stacosys.model.email import Email logger = logging.getLogger(__name__) +current_path = os.path.dirname(__file__) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) +templater = Templater(template_path) def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): @@ -53,7 +57,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): if email.plain_text_content[:2].upper() in ("NO"): logger.info("discard comment: %d" % comment_id) comment.delete_instance() - new_email_body = get_template(lang, "drop_comment").render( + new_email_body = templater.get_template(lang, Template.DROP_COMMENT).render( original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): @@ -67,7 +71,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template(lang, "approve_comment").render( + new_email_body = templater.get_template(lang, Template.APPROVE_COMMENT).render( original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): @@ -90,8 +94,7 @@ def submit_new_comment(lang, mailer): "", ) comment_text = "\n".join(comment_list) - # TODO use constants for template names - email_body = get_template(lang, "new_comment").render( + email_body = templater.get_template(lang, Template.NEW_COMMENT).render( url=comment.url, comment=comment_text ) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index d333772..5d8b761 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -1,13 +1,14 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import os from datetime import datetime import markdown import PyRSS2Gen import stacosys.conf.config as config -from stacosys.core.templater import get_template +from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.site import Site @@ -17,6 +18,9 @@ class Rss: self._lang = lang self._rss_file = rss_file self._rss_proto = rss_proto + current_path = os.path.dirname(__file__) + template_path = os.path.abspath(os.path.join(current_path, "../templates")) + self._templater = Templater(template_path) def generate_all(self): @@ -26,7 +30,9 @@ class Rss: def _generate_site(self, token): site = Site.select().where(Site.token == token).get() - rss_title = get_template(self._lang, "rss_title_message").render(site=site.name) + rss_title = self._templater.get_template( + self._lang, Template.RSS_TITLE_MESSAGE + ).render(site=site.name) md = markdown.Markdown() items = [] diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 6ab8511..025645b 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -1,31 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os from jinja2 import Environment, FileSystemLoader -current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) -env = Environment(loader=FileSystemLoader(template_path)) - -TEMPLATE_DROP_COMMENT = "drop_comment" -TEMPLATE_APPROVE_COMMENT = "approve_comment" -TEMPLATE_NEW_COMMENT = "new_comment" -TEMPLATE_NOTIFY_MESSAGE = "notify_message" -TEMPLATE_RSS_TITLE_MESSAGE = "rss_title_message" - - -def get_template(lang, name): - return env.get_template(lang + "/" + name + ".tpl") - - class Templater: - def __init__(self, lang, template_path): + def __init__(self, template_path): self._env = Environment(loader=FileSystemLoader(template_path)) - self._lang = lang - def get_template(self, name): - return self._env.get_template(self._lang + "/" + name + ".tpl") + def get_template(self, lang, name): + return self._env.get_template(lang + "/" + name + ".tpl") + + +class Template: + DROP_COMMENT = "drop_comment" + APPROVE_COMMENT = "approve_comment" + NEW_COMMENT = "new_comment" + NOTIFY_MESSAGE = "notify_message" + RSS_TITLE_MESSAGE = "rss_title_message" diff --git a/tests/test_templater.py b/tests/test_templater.py index 2d23ba0..6f5b40f 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -4,61 +4,54 @@ import os import pytest -from stacosys.core.templater import ( - Templater, - TEMPLATE_APPROVE_COMMENT, - TEMPLATE_DROP_COMMENT, - TEMPLATE_NEW_COMMENT, - TEMPLATE_NOTIFY_MESSAGE, - TEMPLATE_RSS_TITLE_MESSAGE, -) +from stacosys.core.templater import Templater, Template def get_template_content(lang, template_name, **kwargs): current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - templater = Templater(lang, template_path) - template = templater.get_template(template_name) + templater = Templater(template_path) + template = templater.get_template(lang, template_name) assert template return template.render(kwargs) def test_approve_comment(): - content = get_template_content("fr", TEMPLATE_APPROVE_COMMENT, original="[texte]") + content = get_template_content("fr", Template.APPROVE_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") assert content.endswith("[texte]") - content = get_template_content("en", TEMPLATE_APPROVE_COMMENT, original="[texte]") + content = get_template_content("en", Template.APPROVE_COMMENT, original="[texte]") assert content.startswith("Hi,\n\nThe comment should be published soon.") assert content.endswith("[texte]") def test_drop_comment(): - content = get_template_content("fr", TEMPLATE_DROP_COMMENT, original="[texte]") + content = get_template_content("fr", Template.DROP_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.") assert content.endswith("[texte]") - content = get_template_content("en", TEMPLATE_DROP_COMMENT, original="[texte]") + content = get_template_content("en", Template.DROP_COMMENT, original="[texte]") assert content.startswith("Hi,\n\nThe comment will not be published.") assert content.endswith("[texte]") def test_new_comment(): - content = get_template_content("fr", TEMPLATE_NEW_COMMENT, comment="[comment]") + content = get_template_content("fr", Template.NEW_COMMENT, comment="[comment]") assert content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté") assert content.endswith("[comment]\n\n--\nStacosys") - content = get_template_content("en", TEMPLATE_NEW_COMMENT, comment="[comment]") + content = get_template_content("en", Template.NEW_COMMENT, comment="[comment]") assert content.startswith("Hi,\n\nA new comment has been submitted") assert content.endswith("[comment]\n\n--\nStacosys") def test_notify_message(): - content = get_template_content("fr", TEMPLATE_NOTIFY_MESSAGE) + content = get_template_content("fr", Template.NOTIFY_MESSAGE) assert content == "Nouveau commentaire" - content = get_template_content("en", TEMPLATE_NOTIFY_MESSAGE) + content = get_template_content("en", Template.NOTIFY_MESSAGE) assert content == "New comment" def test_rss_title(): - content = get_template_content("fr", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + content = get_template_content("fr", Template.RSS_TITLE_MESSAGE, site="[site]") assert content == "[site] : commentaires" - content = get_template_content("en", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + content = get_template_content("en", Template.RSS_TITLE_MESSAGE, site="[site]") assert content == "[site] : comments" From cae77c2d055dc816e0edb1531b8915bb165f8484 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 10 Jan 2021 14:26:59 +0100 Subject: [PATCH 304/586] connect new templater implementation --- stacosys/core/cron.py | 13 ++++++++----- stacosys/core/rss.py | 10 ++++++++-- stacosys/core/templater.py | 31 +++++++++++-------------------- tests/test_templater.py | 33 +++++++++++++-------------------- 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 95951ee..920dfc3 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -2,17 +2,21 @@ # -*- coding: utf-8 -*- import logging +import os import re import time from datetime import datetime from stacosys.core import rss -from stacosys.core.templater import get_template +from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment, Site from stacosys.model.email import Email logger = logging.getLogger(__name__) +current_path = os.path.dirname(__file__) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) +templater = Templater(template_path) def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): @@ -53,7 +57,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): if email.plain_text_content[:2].upper() in ("NO"): logger.info("discard comment: %d" % comment_id) comment.delete_instance() - new_email_body = get_template(lang, "drop_comment").render( + new_email_body = templater.get_template(lang, Template.DROP_COMMENT).render( original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): @@ -67,7 +71,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): rss.generate_site(token) # send approval confirmation email to admin - new_email_body = get_template(lang, "approve_comment").render( + new_email_body = templater.get_template(lang, Template.APPROVE_COMMENT).render( original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): @@ -90,8 +94,7 @@ def submit_new_comment(lang, mailer): "", ) comment_text = "\n".join(comment_list) - # TODO use constants for template names - email_body = get_template(lang, "new_comment").render( + email_body = templater.get_template(lang, Template.NEW_COMMENT).render( url=comment.url, comment=comment_text ) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index d333772..5d8b761 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -1,13 +1,14 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import os from datetime import datetime import markdown import PyRSS2Gen import stacosys.conf.config as config -from stacosys.core.templater import get_template +from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.site import Site @@ -17,6 +18,9 @@ class Rss: self._lang = lang self._rss_file = rss_file self._rss_proto = rss_proto + current_path = os.path.dirname(__file__) + template_path = os.path.abspath(os.path.join(current_path, "../templates")) + self._templater = Templater(template_path) def generate_all(self): @@ -26,7 +30,9 @@ class Rss: def _generate_site(self, token): site = Site.select().where(Site.token == token).get() - rss_title = get_template(self._lang, "rss_title_message").render(site=site.name) + rss_title = self._templater.get_template( + self._lang, Template.RSS_TITLE_MESSAGE + ).render(site=site.name) md = markdown.Markdown() items = [] diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 6ab8511..025645b 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -1,31 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os from jinja2 import Environment, FileSystemLoader -current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) -env = Environment(loader=FileSystemLoader(template_path)) - -TEMPLATE_DROP_COMMENT = "drop_comment" -TEMPLATE_APPROVE_COMMENT = "approve_comment" -TEMPLATE_NEW_COMMENT = "new_comment" -TEMPLATE_NOTIFY_MESSAGE = "notify_message" -TEMPLATE_RSS_TITLE_MESSAGE = "rss_title_message" - - -def get_template(lang, name): - return env.get_template(lang + "/" + name + ".tpl") - - class Templater: - def __init__(self, lang, template_path): + def __init__(self, template_path): self._env = Environment(loader=FileSystemLoader(template_path)) - self._lang = lang - def get_template(self, name): - return self._env.get_template(self._lang + "/" + name + ".tpl") + def get_template(self, lang, name): + return self._env.get_template(lang + "/" + name + ".tpl") + + +class Template: + DROP_COMMENT = "drop_comment" + APPROVE_COMMENT = "approve_comment" + NEW_COMMENT = "new_comment" + NOTIFY_MESSAGE = "notify_message" + RSS_TITLE_MESSAGE = "rss_title_message" diff --git a/tests/test_templater.py b/tests/test_templater.py index 2d23ba0..6f5b40f 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -4,61 +4,54 @@ import os import pytest -from stacosys.core.templater import ( - Templater, - TEMPLATE_APPROVE_COMMENT, - TEMPLATE_DROP_COMMENT, - TEMPLATE_NEW_COMMENT, - TEMPLATE_NOTIFY_MESSAGE, - TEMPLATE_RSS_TITLE_MESSAGE, -) +from stacosys.core.templater import Templater, Template def get_template_content(lang, template_name, **kwargs): current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - templater = Templater(lang, template_path) - template = templater.get_template(template_name) + templater = Templater(template_path) + template = templater.get_template(lang, template_name) assert template return template.render(kwargs) def test_approve_comment(): - content = get_template_content("fr", TEMPLATE_APPROVE_COMMENT, original="[texte]") + content = get_template_content("fr", Template.APPROVE_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") assert content.endswith("[texte]") - content = get_template_content("en", TEMPLATE_APPROVE_COMMENT, original="[texte]") + content = get_template_content("en", Template.APPROVE_COMMENT, original="[texte]") assert content.startswith("Hi,\n\nThe comment should be published soon.") assert content.endswith("[texte]") def test_drop_comment(): - content = get_template_content("fr", TEMPLATE_DROP_COMMENT, original="[texte]") + content = get_template_content("fr", Template.DROP_COMMENT, original="[texte]") assert content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.") assert content.endswith("[texte]") - content = get_template_content("en", TEMPLATE_DROP_COMMENT, original="[texte]") + content = get_template_content("en", Template.DROP_COMMENT, original="[texte]") assert content.startswith("Hi,\n\nThe comment will not be published.") assert content.endswith("[texte]") def test_new_comment(): - content = get_template_content("fr", TEMPLATE_NEW_COMMENT, comment="[comment]") + content = get_template_content("fr", Template.NEW_COMMENT, comment="[comment]") assert content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté") assert content.endswith("[comment]\n\n--\nStacosys") - content = get_template_content("en", TEMPLATE_NEW_COMMENT, comment="[comment]") + content = get_template_content("en", Template.NEW_COMMENT, comment="[comment]") assert content.startswith("Hi,\n\nA new comment has been submitted") assert content.endswith("[comment]\n\n--\nStacosys") def test_notify_message(): - content = get_template_content("fr", TEMPLATE_NOTIFY_MESSAGE) + content = get_template_content("fr", Template.NOTIFY_MESSAGE) assert content == "Nouveau commentaire" - content = get_template_content("en", TEMPLATE_NOTIFY_MESSAGE) + content = get_template_content("en", Template.NOTIFY_MESSAGE) assert content == "New comment" def test_rss_title(): - content = get_template_content("fr", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + content = get_template_content("fr", Template.RSS_TITLE_MESSAGE, site="[site]") assert content == "[site] : commentaires" - content = get_template_content("en", TEMPLATE_RSS_TITLE_MESSAGE, site="[site]") + content = get_template_content("en", Template.RSS_TITLE_MESSAGE, site="[site]") assert content == "[site] : comments" From dfdb5bdb28e9594284354013298735dc5f5a4dbd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 10 Jan 2021 15:36:29 +0100 Subject: [PATCH 305/586] refactor config constants --- run.py | 40 +++++++++++++++++---------------- stacosys/conf/config.py | 49 ++++++++++++++++++++--------------------- tests/test_config.py | 46 +++++++++++++++++++------------------- 3 files changed, 68 insertions(+), 67 deletions(-) diff --git a/run.py b/run.py index efa984d..40ee59e 100644 --- a/run.py +++ b/run.py @@ -7,7 +7,7 @@ import argparse import logging from flask import Flask -import stacosys.conf.config as config +from stacosys.conf.config import Config, Parameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer @@ -33,7 +33,7 @@ def configure_logging(level): def stacosys_server(config_pathname): - conf = config.Config.load(config_pathname) + conf = Config.load(config_pathname) # configure logging logger = logging.getLogger(__name__) @@ -43,43 +43,45 @@ def stacosys_server(config_pathname): # initialize database db = database.Database() - db.setup(conf.get(config.DB_URL)) + db.setup(conf.get(Parameter.DB_URL)) logger.info("Start Stacosys application") # generate RSS for all sites rss = Rss( - conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO) + conf.get(Parameter.LANG), + conf.get(Parameter.RSS_FILE), + conf.get(Parameter.RSS_PROTO), ) rss.generate_all() # configure mailer mailer = Mailer( - conf.get(config.IMAP_HOST), - conf.get_int(config.IMAP_PORT), - conf.get_bool(config.IMAP_SSL), - conf.get(config.IMAP_LOGIN), - conf.get(config.IMAP_PASSWORD), - conf.get(config.SMTP_HOST), - conf.get_int(config.SMTP_PORT), - conf.get_bool(config.SMTP_STARTTLS), - conf.get(config.SMTP_LOGIN), - conf.get(config.SMTP_PASSWORD), + conf.get(Parameter.IMAP_HOST), + conf.get_int(Parameter.IMAP_PORT), + conf.get_bool(Parameter.IMAP_SSL), + conf.get(Parameter.IMAP_LOGIN), + conf.get(Parameter.IMAP_PASSWORD), + conf.get(Parameter.SMTP_HOST), + conf.get_int(Parameter.SMTP_PORT), + conf.get_bool(Parameter.SMTP_STARTTLS), + conf.get(Parameter.SMTP_LOGIN), + conf.get(Parameter.SMTP_PASSWORD), ) # configure scheduler scheduler.configure( - conf.get_int(config.IMAP_POLLING), - conf.get_int(config.COMMENT_POLLING), - conf.get(config.LANG), + conf.get_int(Parameter.IMAP_POLLING), + conf.get_int(Parameter.COMMENT_POLLING), + conf.get(Parameter.LANG), mailer, rss, ) # start Flask app.run( - host=conf.get(config.HTTP_HOST), - port=conf.get(config.HTTP_PORT), + host=conf.get(Parameter.HTTP_HOST), + port=conf.get(Parameter.HTTP_PORT), debug=False, use_reloader=False, ) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 15a0ed2..219d09c 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -3,37 +3,36 @@ import profig -# constants -FLASK_APP = "flask.app" -DB_URL = "main.db_url" -DB_BACKUP_JSON_FILE = "main.db_backup_json_file" -LANG = "main.lang" -COMMENT_POLLING = "main.newcomment_polling" +class Parameter: + DB_URL = "main.db_url" + DB_BACKUP_JSON_FILE = "main.db_backup_json_file" + LANG = "main.lang" + COMMENT_POLLING = "main.newcomment_polling" -HTTP_HOST = "http.host" -HTTP_PORT = "http.port" + HTTP_HOST = "http.host" + HTTP_PORT = "http.port" -RSS_PROTO = "rss.proto" -RSS_FILE = "rss.file" + RSS_PROTO = "rss.proto" + RSS_FILE = "rss.file" -IMAP_POLLING = "imap.polling" -IMAP_SSL = "imap.ssl" -IMAP_HOST = "imap.host" -IMAP_PORT = "imap.port" -IMAP_LOGIN = "imap.login" -IMAP_PASSWORD = "imap.password" + IMAP_POLLING = "imap.polling" + IMAP_SSL = "imap.ssl" + IMAP_HOST = "imap.host" + IMAP_PORT = "imap.port" + IMAP_LOGIN = "imap.login" + IMAP_PASSWORD = "imap.password" -SMTP_STARTTLS = "smtp.starttls" -SMTP_HOST = "smtp.host" -SMTP_PORT = "smtp.port" -SMTP_LOGIN = "smtp.login" -SMTP_PASSWORD = "smtp.password" + SMTP_STARTTLS = "smtp.starttls" + SMTP_HOST = "smtp.host" + SMTP_PORT = "smtp.port" + SMTP_LOGIN = "smtp.login" + SMTP_PASSWORD = "smtp.password" -SITE_NAME = "site.name" -SITE_URL = "site.url" -SITE_TOKEN = "site.token" -SITE_ADMIN_EMAIL = "site.admin_email" + SITE_NAME = "site.name" + SITE_URL = "site.url" + SITE_TOKEN = "site.token" + SITE_ADMIN_EMAIL = "site.admin_email" class Config: diff --git a/tests/test_config.py b/tests/test_config.py index 4b6d6f0..2e5452f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- import pytest -import stacosys.conf.config as config +from stacosys.conf.config import Config, Parameter EXPECTED_DB_URL = "sqlite:///db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -12,38 +12,38 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): - conf = config.Config() - conf.put(config.DB_URL, EXPECTED_DB_URL) - conf.put(config.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(config.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(config.SMTP_STARTTLS, "yes") - conf.put(config.IMAP_SSL, "false") + conf = Config() + conf.put(Parameter.DB_URL, EXPECTED_DB_URL) + conf.put(Parameter.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(Parameter.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(Parameter.SMTP_STARTTLS, "yes") + conf.put(Parameter.IMAP_SSL, "false") return conf def test_exists(conf): assert conf is not None - assert conf.exists(config.DB_URL) - assert not conf.exists(config.IMAP_HOST) + assert conf.exists(Parameter.DB_URL) + assert not conf.exists(Parameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(config.DB_URL) == EXPECTED_DB_URL - assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(config.HTTP_HOST) is None - assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(config.IMAP_PORT) == EXPECTED_IMAP_PORT - assert conf.get_int(config.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + assert conf.get(Parameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(Parameter.HTTP_HOST) is None + assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(Parameter.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(Parameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) try: - conf.get_int(config.HTTP_PORT) + conf.get_int(Parameter.HTTP_PORT) assert False except: pass - assert conf.get_bool(config.SMTP_STARTTLS) - assert not conf.get_bool(config.IMAP_SSL) + assert conf.get_bool(Parameter.SMTP_STARTTLS) + assert not conf.get_bool(Parameter.IMAP_SSL) try: - conf.get_bool(config.DB_URL) + conf.get_bool(Parameter.DB_URL) assert False except: pass @@ -51,7 +51,7 @@ def test_get(conf): def test_put(conf): assert conf is not None - assert not conf.exists(config.IMAP_LOGIN) - conf.put(config.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - assert conf.exists(config.IMAP_LOGIN) - assert conf.get(config.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN + assert not conf.exists(Parameter.IMAP_LOGIN) + conf.put(Parameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(Parameter.IMAP_LOGIN) + assert conf.get(Parameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From e85e53a48e0a26a886f588bb5c98bd9dc3091c39 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 10 Jan 2021 15:36:29 +0100 Subject: [PATCH 306/586] refactor config constants --- run.py | 40 +++++++++++++++++---------------- stacosys/conf/config.py | 49 ++++++++++++++++++++--------------------- tests/test_config.py | 46 +++++++++++++++++++------------------- 3 files changed, 68 insertions(+), 67 deletions(-) diff --git a/run.py b/run.py index efa984d..40ee59e 100644 --- a/run.py +++ b/run.py @@ -7,7 +7,7 @@ import argparse import logging from flask import Flask -import stacosys.conf.config as config +from stacosys.conf.config import Config, Parameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer @@ -33,7 +33,7 @@ def configure_logging(level): def stacosys_server(config_pathname): - conf = config.Config.load(config_pathname) + conf = Config.load(config_pathname) # configure logging logger = logging.getLogger(__name__) @@ -43,43 +43,45 @@ def stacosys_server(config_pathname): # initialize database db = database.Database() - db.setup(conf.get(config.DB_URL)) + db.setup(conf.get(Parameter.DB_URL)) logger.info("Start Stacosys application") # generate RSS for all sites rss = Rss( - conf.get(config.LANG), conf.get(config.RSS_FILE), conf.get(config.RSS_PROTO) + conf.get(Parameter.LANG), + conf.get(Parameter.RSS_FILE), + conf.get(Parameter.RSS_PROTO), ) rss.generate_all() # configure mailer mailer = Mailer( - conf.get(config.IMAP_HOST), - conf.get_int(config.IMAP_PORT), - conf.get_bool(config.IMAP_SSL), - conf.get(config.IMAP_LOGIN), - conf.get(config.IMAP_PASSWORD), - conf.get(config.SMTP_HOST), - conf.get_int(config.SMTP_PORT), - conf.get_bool(config.SMTP_STARTTLS), - conf.get(config.SMTP_LOGIN), - conf.get(config.SMTP_PASSWORD), + conf.get(Parameter.IMAP_HOST), + conf.get_int(Parameter.IMAP_PORT), + conf.get_bool(Parameter.IMAP_SSL), + conf.get(Parameter.IMAP_LOGIN), + conf.get(Parameter.IMAP_PASSWORD), + conf.get(Parameter.SMTP_HOST), + conf.get_int(Parameter.SMTP_PORT), + conf.get_bool(Parameter.SMTP_STARTTLS), + conf.get(Parameter.SMTP_LOGIN), + conf.get(Parameter.SMTP_PASSWORD), ) # configure scheduler scheduler.configure( - conf.get_int(config.IMAP_POLLING), - conf.get_int(config.COMMENT_POLLING), - conf.get(config.LANG), + conf.get_int(Parameter.IMAP_POLLING), + conf.get_int(Parameter.COMMENT_POLLING), + conf.get(Parameter.LANG), mailer, rss, ) # start Flask app.run( - host=conf.get(config.HTTP_HOST), - port=conf.get(config.HTTP_PORT), + host=conf.get(Parameter.HTTP_HOST), + port=conf.get(Parameter.HTTP_PORT), debug=False, use_reloader=False, ) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 15a0ed2..219d09c 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -3,37 +3,36 @@ import profig -# constants -FLASK_APP = "flask.app" -DB_URL = "main.db_url" -DB_BACKUP_JSON_FILE = "main.db_backup_json_file" -LANG = "main.lang" -COMMENT_POLLING = "main.newcomment_polling" +class Parameter: + DB_URL = "main.db_url" + DB_BACKUP_JSON_FILE = "main.db_backup_json_file" + LANG = "main.lang" + COMMENT_POLLING = "main.newcomment_polling" -HTTP_HOST = "http.host" -HTTP_PORT = "http.port" + HTTP_HOST = "http.host" + HTTP_PORT = "http.port" -RSS_PROTO = "rss.proto" -RSS_FILE = "rss.file" + RSS_PROTO = "rss.proto" + RSS_FILE = "rss.file" -IMAP_POLLING = "imap.polling" -IMAP_SSL = "imap.ssl" -IMAP_HOST = "imap.host" -IMAP_PORT = "imap.port" -IMAP_LOGIN = "imap.login" -IMAP_PASSWORD = "imap.password" + IMAP_POLLING = "imap.polling" + IMAP_SSL = "imap.ssl" + IMAP_HOST = "imap.host" + IMAP_PORT = "imap.port" + IMAP_LOGIN = "imap.login" + IMAP_PASSWORD = "imap.password" -SMTP_STARTTLS = "smtp.starttls" -SMTP_HOST = "smtp.host" -SMTP_PORT = "smtp.port" -SMTP_LOGIN = "smtp.login" -SMTP_PASSWORD = "smtp.password" + SMTP_STARTTLS = "smtp.starttls" + SMTP_HOST = "smtp.host" + SMTP_PORT = "smtp.port" + SMTP_LOGIN = "smtp.login" + SMTP_PASSWORD = "smtp.password" -SITE_NAME = "site.name" -SITE_URL = "site.url" -SITE_TOKEN = "site.token" -SITE_ADMIN_EMAIL = "site.admin_email" + SITE_NAME = "site.name" + SITE_URL = "site.url" + SITE_TOKEN = "site.token" + SITE_ADMIN_EMAIL = "site.admin_email" class Config: diff --git a/tests/test_config.py b/tests/test_config.py index 4b6d6f0..2e5452f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- import pytest -import stacosys.conf.config as config +from stacosys.conf.config import Config, Parameter EXPECTED_DB_URL = "sqlite:///db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -12,38 +12,38 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): - conf = config.Config() - conf.put(config.DB_URL, EXPECTED_DB_URL) - conf.put(config.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(config.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(config.SMTP_STARTTLS, "yes") - conf.put(config.IMAP_SSL, "false") + conf = Config() + conf.put(Parameter.DB_URL, EXPECTED_DB_URL) + conf.put(Parameter.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(Parameter.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(Parameter.SMTP_STARTTLS, "yes") + conf.put(Parameter.IMAP_SSL, "false") return conf def test_exists(conf): assert conf is not None - assert conf.exists(config.DB_URL) - assert not conf.exists(config.IMAP_HOST) + assert conf.exists(Parameter.DB_URL) + assert not conf.exists(Parameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(config.DB_URL) == EXPECTED_DB_URL - assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(config.HTTP_HOST) is None - assert conf.get(config.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(config.IMAP_PORT) == EXPECTED_IMAP_PORT - assert conf.get_int(config.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + assert conf.get(Parameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(Parameter.HTTP_HOST) is None + assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(Parameter.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(Parameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) try: - conf.get_int(config.HTTP_PORT) + conf.get_int(Parameter.HTTP_PORT) assert False except: pass - assert conf.get_bool(config.SMTP_STARTTLS) - assert not conf.get_bool(config.IMAP_SSL) + assert conf.get_bool(Parameter.SMTP_STARTTLS) + assert not conf.get_bool(Parameter.IMAP_SSL) try: - conf.get_bool(config.DB_URL) + conf.get_bool(Parameter.DB_URL) assert False except: pass @@ -51,7 +51,7 @@ def test_get(conf): def test_put(conf): assert conf is not None - assert not conf.exists(config.IMAP_LOGIN) - conf.put(config.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - assert conf.exists(config.IMAP_LOGIN) - assert conf.get(config.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN + assert not conf.exists(Parameter.IMAP_LOGIN) + conf.put(Parameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(Parameter.IMAP_LOGIN) + assert conf.get(Parameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From acaae53982890ad003aed390470d437a58cb81fa Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 10 Jan 2021 21:03:55 +0100 Subject: [PATCH 307/586] remove site entity --- run.py | 50 +++++++++++++++++++-------------- stacosys/conf/config.py | 2 +- stacosys/core/cron.py | 12 ++++---- stacosys/core/database.py | 4 +-- stacosys/core/rss.py | 29 ++++++++----------- stacosys/interface/api.py | 22 ++++++--------- stacosys/interface/form.py | 9 ++---- stacosys/interface/scheduler.py | 8 +++--- stacosys/model/comment.py | 3 +- stacosys/model/site.py | 13 --------- tests/test_config.py | 44 ++++++++++++++--------------- 11 files changed, 88 insertions(+), 108 deletions(-) delete mode 100644 stacosys/model/site.py diff --git a/run.py b/run.py index 40ee59e..51565cd 100644 --- a/run.py +++ b/run.py @@ -7,7 +7,7 @@ import argparse import logging from flask import Flask -from stacosys.conf.config import Config, Parameter +from stacosys.conf.config import Config, ConfigParameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer @@ -43,45 +43,53 @@ def stacosys_server(config_pathname): # initialize database db = database.Database() - db.setup(conf.get(Parameter.DB_URL)) + db.setup(conf.get(ConfigParameter.DB_URL)) logger.info("Start Stacosys application") # generate RSS for all sites rss = Rss( - conf.get(Parameter.LANG), - conf.get(Parameter.RSS_FILE), - conf.get(Parameter.RSS_PROTO), + conf.get(ConfigParameter.LANG), + conf.get(ConfigParameter.RSS_FILE), + conf.get(ConfigParameter.RSS_PROTO), + conf.get(ConfigParameter.SITE_NAME), + conf.get(ConfigParameter.SITE_URL) ) - rss.generate_all() + rss.generate() # configure mailer mailer = Mailer( - conf.get(Parameter.IMAP_HOST), - conf.get_int(Parameter.IMAP_PORT), - conf.get_bool(Parameter.IMAP_SSL), - conf.get(Parameter.IMAP_LOGIN), - conf.get(Parameter.IMAP_PASSWORD), - conf.get(Parameter.SMTP_HOST), - conf.get_int(Parameter.SMTP_PORT), - conf.get_bool(Parameter.SMTP_STARTTLS), - conf.get(Parameter.SMTP_LOGIN), - conf.get(Parameter.SMTP_PASSWORD), + conf.get(ConfigParameter.IMAP_HOST), + conf.get_int(ConfigParameter.IMAP_PORT), + conf.get_bool(ConfigParameter.IMAP_SSL), + conf.get(ConfigParameter.IMAP_LOGIN), + conf.get(ConfigParameter.IMAP_PASSWORD), + conf.get(ConfigParameter.SMTP_HOST), + conf.get_int(ConfigParameter.SMTP_PORT), + conf.get_bool(ConfigParameter.SMTP_STARTTLS), + conf.get(ConfigParameter.SMTP_LOGIN), + conf.get(ConfigParameter.SMTP_PASSWORD), ) # configure scheduler scheduler.configure( - conf.get_int(Parameter.IMAP_POLLING), - conf.get_int(Parameter.COMMENT_POLLING), - conf.get(Parameter.LANG), + conf.get_int(ConfigParameter.IMAP_POLLING), + conf.get_int(ConfigParameter.COMMENT_POLLING), + conf.get(ConfigParameter.LANG), + conf.get(ConfigParameter.SITE_NAME), + conf.get(ConfigParameter.SITE_TOKEN), + conf.get(ConfigParameter.SITE_ADMIN_EMAIL), mailer, rss, ) + # inject config parameters into flask + app.config.update(SITE_TOKEN=conf.get(ConfigParameter.SITE_TOKEN)) + # start Flask app.run( - host=conf.get(Parameter.HTTP_HOST), - port=conf.get(Parameter.HTTP_PORT), + host=conf.get(ConfigParameter.HTTP_HOST), + port=conf.get(ConfigParameter.HTTP_PORT), debug=False, use_reloader=False, ) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 219d09c..bb2316e 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,7 +4,7 @@ import profig -class Parameter: +class ConfigParameter: DB_URL = "main.db_url" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 920dfc3..9974889 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -9,7 +9,7 @@ from datetime import datetime from stacosys.core import rss from stacosys.core.templater import Templater, Template -from stacosys.model.comment import Comment, Site +from stacosys.model.comment import Comment from stacosys.model.email import Email logger = logging.getLogger(__name__) @@ -18,6 +18,7 @@ current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) + def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): @@ -36,7 +37,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): # retrieve site and comment rows try: - comment = Comment.select().where(Comment.id == comment_id).get() + comment = Comment.get_by_id(comment_id) except: logger.warn("unknown comment %d" % comment_id) return True @@ -80,7 +81,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): return True -def submit_new_comment(lang, mailer): +def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): for comment in Comment.select().where(Comment.notified.is_null()): @@ -99,9 +100,8 @@ def submit_new_comment(lang, mailer): ) # send email - site = Site.get(Site.id == comment.site) - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - if mailer.send(site.admin_email, subject, email_body): + subject = "STACOSYS %s: [%d:%s]" % (site_name, comment.id, site_token) + if mailer.send(site_admin_email, subject, email_body): logger.debug("new comment processed ") # notify site admin and save notification datetime diff --git a/stacosys/core/database.py b/stacosys/core/database.py index b70b1dd..bac46e1 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -6,7 +6,6 @@ from peewee import DatabaseProxy, Model from playhouse.db_url import connect, SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB -from stacosys.conf import config db = SqliteDatabase(None) @@ -25,10 +24,9 @@ class Database: db.init(db_url) db.connect() - from stacosys.model.site import Site from stacosys.model.comment import Comment - db.create_tables([Site, Comment], safe=True) + db.create_tables([Comment], safe=True) # if config.exists(config.DB_BACKUP_JSON_FILE): diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 5d8b761..55076b5 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -10,44 +10,39 @@ import PyRSS2Gen import stacosys.conf.config as config from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment -from stacosys.model.site import Site class Rss: - def __init__(self, lang, rss_file, rss_proto): + def __init__( + self, lang, rss_file, rss_proto, site_name, site_url, + ): self._lang = lang self._rss_file = rss_file self._rss_proto = rss_proto + self._site_name = site_name + self._site_url = site_url current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) self._templater = Templater(template_path) - def generate_all(self): - - for site in Site.select(): - self._generate_site(site.token) - - def _generate_site(self, token): - - site = Site.select().where(Site.token == token).get() + def generate(self): rss_title = self._templater.get_template( self._lang, Template.RSS_TITLE_MESSAGE - ).render(site=site.name) + ).render(site=self._site_name) md = markdown.Markdown() items = [] for row in ( Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) + .where(Comment.published) .order_by(-Comment.published) .limit(10) ): - item_link = "%s://%s%s" % (self._rss_proto, site.url, row.url) + item_link = "%s://%s%s" % (self._rss_proto, self._site_url, row.url) items.append( PyRSS2Gen.RSSItem( title="%s - %s://%s%s" - % (self._rss_proto, row.author_name, site.url, row.url), + % (self._rss_proto, row.author_name, self._site_url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), @@ -57,8 +52,8 @@ class Rss: rss = PyRSS2Gen.RSS2( title=rss_title, - link="%s://%s" % (self._rss_proto, site.url), - description='Commentaires du site "%s"' % site.name, + link="%s://%s" % (self._rss_proto, self._site_url), + description='Commentaires du site "%s"' % self._site_name, lastBuildDate=datetime.now(), items=items, ) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 90d192a..036f657 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -4,9 +4,9 @@ import logging from flask import abort, jsonify, request +from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment -from stacosys.model.site import Site logger = logging.getLogger(__name__) @@ -22,17 +22,15 @@ def query_comments(): comments = [] try: token = request.args.get("token", "") + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) + url = request.args.get("url", "") logger.info("retrieve comments for url %s" % (url)) for comment in ( Comment.select(Comment) - .join(Site) - .where( - (Comment.url == url) - & (Comment.published.is_null(False)) - & (Site.token == token) - ) + .where((Comment.url == url) & (Comment.published.is_null(False))) .order_by(+Comment.published) ): d = { @@ -58,15 +56,13 @@ def query_comments(): def get_comments_count(): try: token = request.args.get("token", "") + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) + url = request.args.get("url", "") count = ( Comment.select(Comment) - .join(Site) - .where( - (Comment.url == url) - & (Comment.published.is_null(False)) - & (Site.token == token) - ) + .where((Comment.url == url) & (Comment.published.is_null(False))) .count() ) r = jsonify({"count": count}) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index df0684c..f00c688 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -5,9 +5,9 @@ import logging from datetime import datetime from flask import abort, redirect, request +from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment -from stacosys.model.site import Site logger = logging.getLogger(__name__) @@ -21,10 +21,8 @@ def new_form_comment(): # validate token: retrieve site entity token = data.get("token", "") - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn("Unknown site %s" % token) - abort(400) + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) # honeypot for spammers captcha = data.get("remarque", "") @@ -49,7 +47,6 @@ def new_form_comment(): # add a row to Comment table created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( - site=site, url=url, author_name=author_name, author_site=author_site, diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 0127282..6762ef0 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -11,7 +11,7 @@ class JobConfig(object): SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, mailer, rss): + def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, site_name, site_token, site_admin_email, mailer, rss): self.JOBS = [ { "id": "fetch_mail", @@ -23,15 +23,15 @@ class JobConfig(object): { "id": "submit_new_comment", "func": "stacosys.core.cron:submit_new_comment", - "args": [lang, mailer], + "args": [lang, site_name, site_token, site_admin_email, mailer], "trigger": "interval", "seconds": new_comment_polling_seconds, }, ] -def configure(imap_polling, comment_polling, lang, mailer, rss): - app.config.from_object(JobConfig(imap_polling, comment_polling, lang, mailer, rss)) +def configure(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss): + app.config.from_object(JobConfig(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss)) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 9493538..999f5a1 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -6,10 +6,10 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from stacosys.model.site import Site from datetime import datetime from stacosys.core.database import BaseModel + class Comment(BaseModel): url = CharField() created = DateTimeField() @@ -19,7 +19,6 @@ class Comment(BaseModel): author_site = CharField(default="") author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name="site") def notify_site_admin(self): self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/stacosys/model/site.py b/stacosys/model/site.py deleted file mode 100644 index b2f4e9b..0000000 --- a/stacosys/model/site.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from stacosys.core.database import BaseModel - - -class Site(BaseModel): - name = CharField(unique=True) - url = CharField() - token = CharField() - admin_email = CharField() diff --git a/tests/test_config.py b/tests/test_config.py index 2e5452f..b373587 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- import pytest -from stacosys.conf.config import Config, Parameter +from stacosys.conf.config import Config, ConfigParameter EXPECTED_DB_URL = "sqlite:///db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -13,37 +13,37 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): conf = Config() - conf.put(Parameter.DB_URL, EXPECTED_DB_URL) - conf.put(Parameter.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(Parameter.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(Parameter.SMTP_STARTTLS, "yes") - conf.put(Parameter.IMAP_SSL, "false") + conf.put(ConfigParameter.DB_URL, EXPECTED_DB_URL) + conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(ConfigParameter.SMTP_STARTTLS, "yes") + conf.put(ConfigParameter.IMAP_SSL, "false") return conf def test_exists(conf): assert conf is not None - assert conf.exists(Parameter.DB_URL) - assert not conf.exists(Parameter.IMAP_HOST) + assert conf.exists(ConfigParameter.DB_URL) + assert not conf.exists(ConfigParameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(Parameter.DB_URL) == EXPECTED_DB_URL - assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(Parameter.HTTP_HOST) is None - assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(Parameter.IMAP_PORT) == EXPECTED_IMAP_PORT - assert conf.get_int(Parameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + assert conf.get(ConfigParameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(ConfigParameter.HTTP_HOST) is None + assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(ConfigParameter.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(ConfigParameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) try: - conf.get_int(Parameter.HTTP_PORT) + conf.get_int(ConfigParameter.HTTP_PORT) assert False except: pass - assert conf.get_bool(Parameter.SMTP_STARTTLS) - assert not conf.get_bool(Parameter.IMAP_SSL) + assert conf.get_bool(ConfigParameter.SMTP_STARTTLS) + assert not conf.get_bool(ConfigParameter.IMAP_SSL) try: - conf.get_bool(Parameter.DB_URL) + conf.get_bool(ConfigParameter.DB_URL) assert False except: pass @@ -51,7 +51,7 @@ def test_get(conf): def test_put(conf): assert conf is not None - assert not conf.exists(Parameter.IMAP_LOGIN) - conf.put(Parameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - assert conf.exists(Parameter.IMAP_LOGIN) - assert conf.get(Parameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN + assert not conf.exists(ConfigParameter.IMAP_LOGIN) + conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(ConfigParameter.IMAP_LOGIN) + assert conf.get(ConfigParameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From e6986e35879d0d6afe0633ce8a7f25ec6997116a Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 10 Jan 2021 21:03:55 +0100 Subject: [PATCH 308/586] remove site entity --- run.py | 50 +++++++++++++++++++-------------- stacosys/conf/config.py | 2 +- stacosys/core/cron.py | 12 ++++---- stacosys/core/database.py | 4 +-- stacosys/core/rss.py | 29 ++++++++----------- stacosys/interface/api.py | 22 ++++++--------- stacosys/interface/form.py | 9 ++---- stacosys/interface/scheduler.py | 8 +++--- stacosys/model/comment.py | 3 +- stacosys/model/site.py | 13 --------- tests/test_config.py | 44 ++++++++++++++--------------- 11 files changed, 88 insertions(+), 108 deletions(-) delete mode 100644 stacosys/model/site.py diff --git a/run.py b/run.py index 40ee59e..51565cd 100644 --- a/run.py +++ b/run.py @@ -7,7 +7,7 @@ import argparse import logging from flask import Flask -from stacosys.conf.config import Config, Parameter +from stacosys.conf.config import Config, ConfigParameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer @@ -43,45 +43,53 @@ def stacosys_server(config_pathname): # initialize database db = database.Database() - db.setup(conf.get(Parameter.DB_URL)) + db.setup(conf.get(ConfigParameter.DB_URL)) logger.info("Start Stacosys application") # generate RSS for all sites rss = Rss( - conf.get(Parameter.LANG), - conf.get(Parameter.RSS_FILE), - conf.get(Parameter.RSS_PROTO), + conf.get(ConfigParameter.LANG), + conf.get(ConfigParameter.RSS_FILE), + conf.get(ConfigParameter.RSS_PROTO), + conf.get(ConfigParameter.SITE_NAME), + conf.get(ConfigParameter.SITE_URL) ) - rss.generate_all() + rss.generate() # configure mailer mailer = Mailer( - conf.get(Parameter.IMAP_HOST), - conf.get_int(Parameter.IMAP_PORT), - conf.get_bool(Parameter.IMAP_SSL), - conf.get(Parameter.IMAP_LOGIN), - conf.get(Parameter.IMAP_PASSWORD), - conf.get(Parameter.SMTP_HOST), - conf.get_int(Parameter.SMTP_PORT), - conf.get_bool(Parameter.SMTP_STARTTLS), - conf.get(Parameter.SMTP_LOGIN), - conf.get(Parameter.SMTP_PASSWORD), + conf.get(ConfigParameter.IMAP_HOST), + conf.get_int(ConfigParameter.IMAP_PORT), + conf.get_bool(ConfigParameter.IMAP_SSL), + conf.get(ConfigParameter.IMAP_LOGIN), + conf.get(ConfigParameter.IMAP_PASSWORD), + conf.get(ConfigParameter.SMTP_HOST), + conf.get_int(ConfigParameter.SMTP_PORT), + conf.get_bool(ConfigParameter.SMTP_STARTTLS), + conf.get(ConfigParameter.SMTP_LOGIN), + conf.get(ConfigParameter.SMTP_PASSWORD), ) # configure scheduler scheduler.configure( - conf.get_int(Parameter.IMAP_POLLING), - conf.get_int(Parameter.COMMENT_POLLING), - conf.get(Parameter.LANG), + conf.get_int(ConfigParameter.IMAP_POLLING), + conf.get_int(ConfigParameter.COMMENT_POLLING), + conf.get(ConfigParameter.LANG), + conf.get(ConfigParameter.SITE_NAME), + conf.get(ConfigParameter.SITE_TOKEN), + conf.get(ConfigParameter.SITE_ADMIN_EMAIL), mailer, rss, ) + # inject config parameters into flask + app.config.update(SITE_TOKEN=conf.get(ConfigParameter.SITE_TOKEN)) + # start Flask app.run( - host=conf.get(Parameter.HTTP_HOST), - port=conf.get(Parameter.HTTP_PORT), + host=conf.get(ConfigParameter.HTTP_HOST), + port=conf.get(ConfigParameter.HTTP_PORT), debug=False, use_reloader=False, ) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 219d09c..bb2316e 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -4,7 +4,7 @@ import profig -class Parameter: +class ConfigParameter: DB_URL = "main.db_url" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 920dfc3..9974889 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -9,7 +9,7 @@ from datetime import datetime from stacosys.core import rss from stacosys.core.templater import Templater, Template -from stacosys.model.comment import Comment, Site +from stacosys.model.comment import Comment from stacosys.model.email import Email logger = logging.getLogger(__name__) @@ -18,6 +18,7 @@ current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) + def fetch_mail_answers(lang, mailer, rss): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): @@ -36,7 +37,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): # retrieve site and comment rows try: - comment = Comment.select().where(Comment.id == comment_id).get() + comment = Comment.get_by_id(comment_id) except: logger.warn("unknown comment %d" % comment_id) return True @@ -80,7 +81,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email): return True -def submit_new_comment(lang, mailer): +def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): for comment in Comment.select().where(Comment.notified.is_null()): @@ -99,9 +100,8 @@ def submit_new_comment(lang, mailer): ) # send email - site = Site.get(Site.id == comment.site) - subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token) - if mailer.send(site.admin_email, subject, email_body): + subject = "STACOSYS %s: [%d:%s]" % (site_name, comment.id, site_token) + if mailer.send(site_admin_email, subject, email_body): logger.debug("new comment processed ") # notify site admin and save notification datetime diff --git a/stacosys/core/database.py b/stacosys/core/database.py index b70b1dd..bac46e1 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -6,7 +6,6 @@ from peewee import DatabaseProxy, Model from playhouse.db_url import connect, SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB -from stacosys.conf import config db = SqliteDatabase(None) @@ -25,10 +24,9 @@ class Database: db.init(db_url) db.connect() - from stacosys.model.site import Site from stacosys.model.comment import Comment - db.create_tables([Site, Comment], safe=True) + db.create_tables([Comment], safe=True) # if config.exists(config.DB_BACKUP_JSON_FILE): diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 5d8b761..55076b5 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -10,44 +10,39 @@ import PyRSS2Gen import stacosys.conf.config as config from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment -from stacosys.model.site import Site class Rss: - def __init__(self, lang, rss_file, rss_proto): + def __init__( + self, lang, rss_file, rss_proto, site_name, site_url, + ): self._lang = lang self._rss_file = rss_file self._rss_proto = rss_proto + self._site_name = site_name + self._site_url = site_url current_path = os.path.dirname(__file__) template_path = os.path.abspath(os.path.join(current_path, "../templates")) self._templater = Templater(template_path) - def generate_all(self): - - for site in Site.select(): - self._generate_site(site.token) - - def _generate_site(self, token): - - site = Site.select().where(Site.token == token).get() + def generate(self): rss_title = self._templater.get_template( self._lang, Template.RSS_TITLE_MESSAGE - ).render(site=site.name) + ).render(site=self._site_name) md = markdown.Markdown() items = [] for row in ( Comment.select() - .join(Site) - .where(Site.token == token, Comment.published) + .where(Comment.published) .order_by(-Comment.published) .limit(10) ): - item_link = "%s://%s%s" % (self._rss_proto, site.url, row.url) + item_link = "%s://%s%s" % (self._rss_proto, self._site_url, row.url) items.append( PyRSS2Gen.RSSItem( title="%s - %s://%s%s" - % (self._rss_proto, row.author_name, site.url, row.url), + % (self._rss_proto, row.author_name, self._site_url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), @@ -57,8 +52,8 @@ class Rss: rss = PyRSS2Gen.RSS2( title=rss_title, - link="%s://%s" % (self._rss_proto, site.url), - description='Commentaires du site "%s"' % site.name, + link="%s://%s" % (self._rss_proto, self._site_url), + description='Commentaires du site "%s"' % self._site_name, lastBuildDate=datetime.now(), items=items, ) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 90d192a..036f657 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -4,9 +4,9 @@ import logging from flask import abort, jsonify, request +from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment -from stacosys.model.site import Site logger = logging.getLogger(__name__) @@ -22,17 +22,15 @@ def query_comments(): comments = [] try: token = request.args.get("token", "") + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) + url = request.args.get("url", "") logger.info("retrieve comments for url %s" % (url)) for comment in ( Comment.select(Comment) - .join(Site) - .where( - (Comment.url == url) - & (Comment.published.is_null(False)) - & (Site.token == token) - ) + .where((Comment.url == url) & (Comment.published.is_null(False))) .order_by(+Comment.published) ): d = { @@ -58,15 +56,13 @@ def query_comments(): def get_comments_count(): try: token = request.args.get("token", "") + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) + url = request.args.get("url", "") count = ( Comment.select(Comment) - .join(Site) - .where( - (Comment.url == url) - & (Comment.published.is_null(False)) - & (Site.token == token) - ) + .where((Comment.url == url) & (Comment.published.is_null(False))) .count() ) r = jsonify({"count": count}) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index df0684c..f00c688 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -5,9 +5,9 @@ import logging from datetime import datetime from flask import abort, redirect, request +from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment -from stacosys.model.site import Site logger = logging.getLogger(__name__) @@ -21,10 +21,8 @@ def new_form_comment(): # validate token: retrieve site entity token = data.get("token", "") - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn("Unknown site %s" % token) - abort(400) + if token != app.config.get(ConfigParameter.SITE_TOKEN): + abort(401) # honeypot for spammers captcha = data.get("remarque", "") @@ -49,7 +47,6 @@ def new_form_comment(): # add a row to Comment table created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = Comment( - site=site, url=url, author_name=author_name, author_site=author_site, diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 0127282..6762ef0 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -11,7 +11,7 @@ class JobConfig(object): SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, mailer, rss): + def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, site_name, site_token, site_admin_email, mailer, rss): self.JOBS = [ { "id": "fetch_mail", @@ -23,15 +23,15 @@ class JobConfig(object): { "id": "submit_new_comment", "func": "stacosys.core.cron:submit_new_comment", - "args": [lang, mailer], + "args": [lang, site_name, site_token, site_admin_email, mailer], "trigger": "interval", "seconds": new_comment_polling_seconds, }, ] -def configure(imap_polling, comment_polling, lang, mailer, rss): - app.config.from_object(JobConfig(imap_polling, comment_polling, lang, mailer, rss)) +def configure(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss): + app.config.from_object(JobConfig(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss)) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 9493538..999f5a1 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -6,10 +6,10 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from peewee import ForeignKeyField -from stacosys.model.site import Site from datetime import datetime from stacosys.core.database import BaseModel + class Comment(BaseModel): url = CharField() created = DateTimeField() @@ -19,7 +19,6 @@ class Comment(BaseModel): author_site = CharField(default="") author_gravatar = CharField(default="") content = TextField() - site = ForeignKeyField(Site, related_name="site") def notify_site_admin(self): self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/stacosys/model/site.py b/stacosys/model/site.py deleted file mode 100644 index b2f4e9b..0000000 --- a/stacosys/model/site.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from stacosys.core.database import BaseModel - - -class Site(BaseModel): - name = CharField(unique=True) - url = CharField() - token = CharField() - admin_email = CharField() diff --git a/tests/test_config.py b/tests/test_config.py index 2e5452f..b373587 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- import pytest -from stacosys.conf.config import Config, Parameter +from stacosys.conf.config import Config, ConfigParameter EXPECTED_DB_URL = "sqlite:///db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -13,37 +13,37 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): conf = Config() - conf.put(Parameter.DB_URL, EXPECTED_DB_URL) - conf.put(Parameter.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(Parameter.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(Parameter.SMTP_STARTTLS, "yes") - conf.put(Parameter.IMAP_SSL, "false") + conf.put(ConfigParameter.DB_URL, EXPECTED_DB_URL) + conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(ConfigParameter.SMTP_STARTTLS, "yes") + conf.put(ConfigParameter.IMAP_SSL, "false") return conf def test_exists(conf): assert conf is not None - assert conf.exists(Parameter.DB_URL) - assert not conf.exists(Parameter.IMAP_HOST) + assert conf.exists(ConfigParameter.DB_URL) + assert not conf.exists(ConfigParameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(Parameter.DB_URL) == EXPECTED_DB_URL - assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(Parameter.HTTP_HOST) is None - assert conf.get(Parameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(Parameter.IMAP_PORT) == EXPECTED_IMAP_PORT - assert conf.get_int(Parameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) + assert conf.get(ConfigParameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(ConfigParameter.HTTP_HOST) is None + assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT + assert conf.get(ConfigParameter.IMAP_PORT) == EXPECTED_IMAP_PORT + assert conf.get_int(ConfigParameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) try: - conf.get_int(Parameter.HTTP_PORT) + conf.get_int(ConfigParameter.HTTP_PORT) assert False except: pass - assert conf.get_bool(Parameter.SMTP_STARTTLS) - assert not conf.get_bool(Parameter.IMAP_SSL) + assert conf.get_bool(ConfigParameter.SMTP_STARTTLS) + assert not conf.get_bool(ConfigParameter.IMAP_SSL) try: - conf.get_bool(Parameter.DB_URL) + conf.get_bool(ConfigParameter.DB_URL) assert False except: pass @@ -51,7 +51,7 @@ def test_get(conf): def test_put(conf): assert conf is not None - assert not conf.exists(Parameter.IMAP_LOGIN) - conf.put(Parameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - assert conf.exists(Parameter.IMAP_LOGIN) - assert conf.get(Parameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN + assert not conf.exists(ConfigParameter.IMAP_LOGIN) + conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + assert conf.exists(ConfigParameter.IMAP_LOGIN) + assert conf.get(ConfigParameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN From 8ee07ec9edfb0c84b0643fcad707d13e153e7e69 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 14 Jan 2021 18:45:06 +0100 Subject: [PATCH 309/586] use enums --- stacosys/conf/config.py | 23 ++++++++++++----------- stacosys/core/database.py | 1 - stacosys/core/templater.py | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index bb2316e..5962f4c 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from enum import Enum import profig -class ConfigParameter: +class ConfigParameter(Enum): DB_URL = "main.db_url" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" @@ -47,18 +48,18 @@ class Config: config._params.update(cfg) return config - def exists(self, key): - return key in self._params + def exists(self, key: ConfigParameter): + return key.value in self._params - def get(self, key): - return self._params[key] if key in self._params else None + def get(self, key: ConfigParameter): + return self._params[key.value] if key.value in self._params else None - def put(self, key, value): - self._params[key] = value + def put(self, key: ConfigParameter, value): + self._params[key.value] = value - def get_int(self, key): - return int(self._params[key]) + def get_int(self, key: ConfigParameter): + return int(self._params[key.value]) - def get_bool(self, key): - return self._params[key].lower() in ("yes", "true") + def get_bool(self, key: ConfigParameter): + return self._params[key.value].lower() in ("yes", "true") diff --git a/stacosys/core/database.py b/stacosys/core/database.py index bac46e1..1de6db4 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -25,7 +25,6 @@ class Database: db.connect() from stacosys.model.comment import Comment - db.create_tables([Comment], safe=True) diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 025645b..d3d4564 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -1,22 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - +from enum import Enum from jinja2 import Environment, FileSystemLoader -class Templater: - def __init__(self, template_path): - self._env = Environment(loader=FileSystemLoader(template_path)) - - def get_template(self, lang, name): - return self._env.get_template(lang + "/" + name + ".tpl") - - -class Template: +class Template(Enum): DROP_COMMENT = "drop_comment" APPROVE_COMMENT = "approve_comment" NEW_COMMENT = "new_comment" NOTIFY_MESSAGE = "notify_message" RSS_TITLE_MESSAGE = "rss_title_message" + +class Templater: + def __init__(self, template_path): + self._env = Environment(loader=FileSystemLoader(template_path)) + + def get_template(self, lang, template: Template): + return self._env.get_template(lang + "/" + template.value + ".tpl") + From 435cddc4e4c04dedc712d8c2bbb54942820d4617 Mon Sep 17 00:00:00 2001 From: Yax Date: Thu, 14 Jan 2021 18:45:06 +0100 Subject: [PATCH 310/586] use enums --- stacosys/conf/config.py | 23 ++++++++++++----------- stacosys/core/database.py | 1 - stacosys/core/templater.py | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index bb2316e..5962f4c 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from enum import Enum import profig -class ConfigParameter: +class ConfigParameter(Enum): DB_URL = "main.db_url" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" @@ -47,18 +48,18 @@ class Config: config._params.update(cfg) return config - def exists(self, key): - return key in self._params + def exists(self, key: ConfigParameter): + return key.value in self._params - def get(self, key): - return self._params[key] if key in self._params else None + def get(self, key: ConfigParameter): + return self._params[key.value] if key.value in self._params else None - def put(self, key, value): - self._params[key] = value + def put(self, key: ConfigParameter, value): + self._params[key.value] = value - def get_int(self, key): - return int(self._params[key]) + def get_int(self, key: ConfigParameter): + return int(self._params[key.value]) - def get_bool(self, key): - return self._params[key].lower() in ("yes", "true") + def get_bool(self, key: ConfigParameter): + return self._params[key.value].lower() in ("yes", "true") diff --git a/stacosys/core/database.py b/stacosys/core/database.py index bac46e1..1de6db4 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -25,7 +25,6 @@ class Database: db.connect() from stacosys.model.comment import Comment - db.create_tables([Comment], safe=True) diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index 025645b..d3d4564 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -1,22 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - +from enum import Enum from jinja2 import Environment, FileSystemLoader -class Templater: - def __init__(self, template_path): - self._env = Environment(loader=FileSystemLoader(template_path)) - - def get_template(self, lang, name): - return self._env.get_template(lang + "/" + name + ".tpl") - - -class Template: +class Template(Enum): DROP_COMMENT = "drop_comment" APPROVE_COMMENT = "approve_comment" NEW_COMMENT = "new_comment" NOTIFY_MESSAGE = "notify_message" RSS_TITLE_MESSAGE = "rss_title_message" + +class Templater: + def __init__(self, template_path): + self._env = Environment(loader=FileSystemLoader(template_path)) + + def get_template(self, lang, template: Template): + return self._env.get_template(lang + "/" + template.value + ".tpl") + From 0a4533b3897591238211c760f28ea603479b4b36 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 18 Jan 2021 19:25:45 +0100 Subject: [PATCH 311/586] database migration script --- dbmigrate.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 dbmigrate.py diff --git a/dbmigrate.py b/dbmigrate.py new file mode 100644 index 0000000..396f576 --- /dev/null +++ b/dbmigrate.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import sqlite3 + +connection = sqlite3.connect("db.sqlite") +cursor = connection.cursor() + +# What script performs: +# - first, remove site table: crash here if table doesn't exist (compatibility test without effort) +# - remove site_id colum from comment table +script = """ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; +DROP TABLE site; +ALTER TABLE comment RENAME TO _comment_old; +CREATE TABLE comment ( + id INTEGER NOT NULL PRIMARY KEY, + url VARCHAR(255) NOT NULL, + notified DATETIME, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, + author_site VARCHAR(255) NOT NULL, + author_gravatar varchar(255), + content TEXT NOT NULL +); +INSERT INTO comment (id, url, notified, created, published, author_name, author_site, author_gravatar, content) + SELECT id, url, notified, created, published, author_name, author_site, author_gravatar, content + FROM _comment_old; +DROP TABLE _comment_old; +COMMIT; +PRAGMA foreign_keys = ON; +""" + +cursor.executescript(script) +connection.close() \ No newline at end of file From 7bd3c6ffef7a4c1b42298619c175faf2836cc14e Mon Sep 17 00:00:00 2001 From: Yax Date: Mon, 18 Jan 2021 19:25:45 +0100 Subject: [PATCH 312/586] database migration script --- dbmigrate.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 dbmigrate.py diff --git a/dbmigrate.py b/dbmigrate.py new file mode 100644 index 0000000..396f576 --- /dev/null +++ b/dbmigrate.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import sqlite3 + +connection = sqlite3.connect("db.sqlite") +cursor = connection.cursor() + +# What script performs: +# - first, remove site table: crash here if table doesn't exist (compatibility test without effort) +# - remove site_id colum from comment table +script = """ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; +DROP TABLE site; +ALTER TABLE comment RENAME TO _comment_old; +CREATE TABLE comment ( + id INTEGER NOT NULL PRIMARY KEY, + url VARCHAR(255) NOT NULL, + notified DATETIME, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, + author_site VARCHAR(255) NOT NULL, + author_gravatar varchar(255), + content TEXT NOT NULL +); +INSERT INTO comment (id, url, notified, created, published, author_name, author_site, author_gravatar, content) + SELECT id, url, notified, created, published, author_name, author_site, author_gravatar, content + FROM _comment_old; +DROP TABLE _comment_old; +COMMIT; +PRAGMA foreign_keys = ON; +""" + +cursor.executescript(script) +connection.close() \ No newline at end of file From 35173eff71d8d0bdec76f4b8bbbe42c239497831 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 18:58:59 +0100 Subject: [PATCH 313/586] move db migration tool --- dbmigrate.py => dbmigration/migrate_from_1.1_to_2.0.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dbmigrate.py => dbmigration/migrate_from_1.1_to_2.0.py (100%) diff --git a/dbmigrate.py b/dbmigration/migrate_from_1.1_to_2.0.py similarity index 100% rename from dbmigrate.py rename to dbmigration/migrate_from_1.1_to_2.0.py From e6d950fe5625f92d995be764f8a94ceb3ce67f77 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 18:58:59 +0100 Subject: [PATCH 314/586] move db migration tool --- dbmigrate.py => dbmigration/migrate_from_1.1_to_2.0.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dbmigrate.py => dbmigration/migrate_from_1.1_to_2.0.py (100%) diff --git a/dbmigrate.py b/dbmigration/migrate_from_1.1_to_2.0.py similarity index 100% rename from dbmigrate.py rename to dbmigration/migrate_from_1.1_to_2.0.py From 6b4ae63190ae5f6d7d4fcfeea2481b75119ff7b6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 18:59:26 +0100 Subject: [PATCH 315/586] remove unused __init__.py files --- stacosys/__init__.py | 2 +- stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/model/__init__.py | 0 tests/__init__.py | 0 5 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 stacosys/conf/__init__.py delete mode 100644 stacosys/core/__init__.py delete mode 100644 stacosys/model/__init__.py delete mode 100644 tests/__init__.py diff --git a/stacosys/__init__.py b/stacosys/__init__.py index 1a72d32..3b3dacb 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = '1.1.0' +__version__ = '2.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/model/__init__.py b/stacosys/model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 From de6e9ba4f081c4b6e00d21597c4ee86a6951db9c Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 18:59:26 +0100 Subject: [PATCH 316/586] remove unused __init__.py files --- stacosys/__init__.py | 2 +- stacosys/conf/__init__.py | 0 stacosys/core/__init__.py | 0 stacosys/model/__init__.py | 0 tests/__init__.py | 0 5 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 stacosys/conf/__init__.py delete mode 100644 stacosys/core/__init__.py delete mode 100644 stacosys/model/__init__.py delete mode 100644 tests/__init__.py diff --git a/stacosys/__init__.py b/stacosys/__init__.py index 1a72d32..3b3dacb 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = '1.1.0' +__version__ = '2.0' diff --git a/stacosys/conf/__init__.py b/stacosys/conf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/core/__init__.py b/stacosys/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacosys/model/__init__.py b/stacosys/model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 From 5dca8aafe1527324f32326d368547b91f98c9ce6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 18:59:57 +0100 Subject: [PATCH 317/586] get rid of site token retrieved from DB --- stacosys/core/cron.py | 13 ++++++------- stacosys/interface/scheduler.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 9974889..292d4e8 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -19,14 +19,14 @@ template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) -def fetch_mail_answers(lang, mailer, rss): +def fetch_mail_answers(lang, mailer, rss, site_token): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): - if _reply_comment_email(lang, mailer, rss, msg): + if _reply_comment_email(lang, mailer, rss, msg, site_token): mailer.delete(msg.id) -def _reply_comment_email(lang, mailer, rss, email: Email): +def _reply_comment_email(lang, mailer, rss, email: Email, site_token): m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: @@ -34,6 +34,9 @@ def _reply_comment_email(lang, mailer, rss, email: Email): return comment_id = int(m.group(1)) token = m.group(2) + if token != site_token: + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + return # retrieve site and comment rows try: @@ -46,10 +49,6 @@ def _reply_comment_email(lang, mailer, rss, email: Email): logger.warn("ignore already published email. token %d" % comment_id) return - if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) - return - if not email.plain_text_content: logger.warn("ignore empty email") return diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 6762ef0..832a0d6 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -16,7 +16,7 @@ class JobConfig(object): { "id": "fetch_mail", "func": "stacosys.core.cron:fetch_mail_answers", - "args": [lang, mailer, rss], + "args": [lang, mailer, rss, site_token], "trigger": "interval", "seconds": imap_polling_seconds, }, From 450280e9b9f473d438d6e763c1e99f117dca2e97 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 18:59:57 +0100 Subject: [PATCH 318/586] get rid of site token retrieved from DB --- stacosys/core/cron.py | 13 ++++++------- stacosys/interface/scheduler.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 9974889..292d4e8 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -19,14 +19,14 @@ template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) -def fetch_mail_answers(lang, mailer, rss): +def fetch_mail_answers(lang, mailer, rss, site_token): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): - if _reply_comment_email(lang, mailer, rss, msg): + if _reply_comment_email(lang, mailer, rss, msg, site_token): mailer.delete(msg.id) -def _reply_comment_email(lang, mailer, rss, email: Email): +def _reply_comment_email(lang, mailer, rss, email: Email, site_token): m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: @@ -34,6 +34,9 @@ def _reply_comment_email(lang, mailer, rss, email: Email): return comment_id = int(m.group(1)) token = m.group(2) + if token != site_token: + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + return # retrieve site and comment rows try: @@ -46,10 +49,6 @@ def _reply_comment_email(lang, mailer, rss, email: Email): logger.warn("ignore already published email. token %d" % comment_id) return - if comment.site.token != token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) - return - if not email.plain_text_content: logger.warn("ignore empty email") return diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 6762ef0..832a0d6 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -16,7 +16,7 @@ class JobConfig(object): { "id": "fetch_mail", "func": "stacosys.core.cron:fetch_mail_answers", - "args": [lang, mailer, rss], + "args": [lang, mailer, rss, site_token], "trigger": "interval", "seconds": imap_polling_seconds, }, From 599838af1fdec9749bc6b02e4f498ce05aa5ae19 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:00:35 +0100 Subject: [PATCH 319/586] major version 2.0 --- tests/test_stacosys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 124b4fa..3b5b8b1 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '1.1.0' + assert __version__ == '2.0' From 93a4971d79538ecc17e923d36836fff86dfe9b88 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 19:00:35 +0100 Subject: [PATCH 320/586] major version 2.0 --- tests/test_stacosys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 124b4fa..3b5b8b1 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '1.1.0' + assert __version__ == '2.0' From 3382a0a3f410f6c517cd7a154878a699034555bd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:00:57 +0100 Subject: [PATCH 321/586] fix flask parameter retrieval --- stacosys/interface/api.py | 5 ++--- stacosys/interface/form.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 036f657..3e5ef04 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -4,7 +4,6 @@ import logging from flask import abort, jsonify, request -from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment @@ -22,7 +21,7 @@ def query_comments(): comments = [] try: token = request.args.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) url = request.args.get("url", "") @@ -56,7 +55,7 @@ def query_comments(): def get_comments_count(): try: token = request.args.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) url = request.args.get("url", "") diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index f00c688..5bb04c9 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -5,7 +5,6 @@ import logging from datetime import datetime from flask import abort, redirect, request -from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment @@ -21,7 +20,7 @@ def new_form_comment(): # validate token: retrieve site entity token = data.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) # honeypot for spammers From 3d818ad4176e7c78441c862fd22e89a1f91a045a Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 19:00:57 +0100 Subject: [PATCH 322/586] fix flask parameter retrieval --- stacosys/interface/api.py | 5 ++--- stacosys/interface/form.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 036f657..3e5ef04 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -4,7 +4,6 @@ import logging from flask import abort, jsonify, request -from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment @@ -22,7 +21,7 @@ def query_comments(): comments = [] try: token = request.args.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) url = request.args.get("url", "") @@ -56,7 +55,7 @@ def query_comments(): def get_comments_count(): try: token = request.args.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) url = request.args.get("url", "") diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index f00c688..5bb04c9 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -5,7 +5,6 @@ import logging from datetime import datetime from flask import abort, redirect, request -from stacosys.conf.config import ConfigParameter from stacosys.interface import app from stacosys.model.comment import Comment @@ -21,7 +20,7 @@ def new_form_comment(): # validate token: retrieve site entity token = data.get("token", "") - if token != app.config.get(ConfigParameter.SITE_TOKEN): + if token != app.config.get("SITE_TOKEN"): abort(401) # honeypot for spammers From 75ee7acb6d4fae238d8cdff9799a1c36d8ce2a65 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:01:20 +0100 Subject: [PATCH 323/586] stop using typing module. Python 3.9 required. --- stacosys/model/email.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 8852506..c3feb34 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -1,29 +1,27 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from typing import NamedTuple -from typing import List from datetime import datetime -class Part(NamedTuple): +class Part(): content: str content_type: str -class Attachment(NamedTuple): +class Attachment(): filename: str content: str content_type: str -class Email(NamedTuple): +class Email(): id: int encoding: str date: datetime from_addr: str to_addr: str subject: str - parts: List[Part] - attachments: List[Attachment] + parts: list[Part] + attachments: list[Attachment] plain_text_content: str From af414d11e1a5ad87d8741494bb1bf2f905a5e9c3 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 19:01:20 +0100 Subject: [PATCH 324/586] stop using typing module. Python 3.9 required. --- stacosys/model/email.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 8852506..c3feb34 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -1,29 +1,27 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from typing import NamedTuple -from typing import List from datetime import datetime -class Part(NamedTuple): +class Part(): content: str content_type: str -class Attachment(NamedTuple): +class Attachment(): filename: str content: str content_type: str -class Email(NamedTuple): +class Email(): id: int encoding: str date: datetime from_addr: str to_addr: str subject: str - parts: List[Part] - attachments: List[Attachment] + parts: list[Part] + attachments: list[Attachment] plain_text_content: str From 5efc3dd3307604d80203af793a205501ef689164 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:27:49 +0100 Subject: [PATCH 325/586] add mypy tool --- poetry.lock | 59 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + stacosys/interface/scheduler.py | 38 ++++++++++++++++++--- stacosys/model/email.py | 11 +++--- 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 997c104..82d1597 100644 --- a/poetry.lock +++ b/poetry.lock @@ -210,6 +210,30 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "19.2" @@ -394,6 +418,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "tzlocal" version = "2.0.0" @@ -457,7 +489,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "6a714b61f2ae04be8377aa2fb513c09bdae22cb71685b4b7ddfed843bfd9ff08" +content-hash = "f3a87d43b25f363c4ef34c8e8aa8b16aadb975c204d34234babbe0ee33038a02" [metadata.files] appdirs = [ @@ -562,6 +594,26 @@ more-itertools = [ {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, ] +mypy = [ + {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, + {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, + {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, + {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, + {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, + {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, + {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, + {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, + {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, + {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, + {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, + {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, + {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, + {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] packaging = [ {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, @@ -680,6 +732,11 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] tzlocal = [ {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, diff --git a/pyproject.toml b/pyproject.toml index 99ae291..a95099d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ tinydb = "^4.3.0" pytest = "^5.2" black = {version = "^19.10b0", allow-prereleases = true} rope = "^0.16.0" +mypy = "^0.790" [build-system] requires = ["poetry>=0.12"] diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 832a0d6..33ea3ef 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -7,11 +7,21 @@ from stacosys.interface import app class JobConfig(object): - JOBS = [] + JOBS: list = [] SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, site_name, site_token, site_admin_email, mailer, rss): + def __init__( + self, + imap_polling_seconds, + new_comment_polling_seconds, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, + ): self.JOBS = [ { "id": "fetch_mail", @@ -30,8 +40,28 @@ class JobConfig(object): ] -def configure(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss): - app.config.from_object(JobConfig(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss)) +def configure( + imap_polling, + comment_polling, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, +): + app.config.from_object( + JobConfig( + imap_polling, + comment_polling, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, + ) + ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() diff --git a/stacosys/model/email.py b/stacosys/model/email.py index c3feb34..4d0b081 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -2,26 +2,27 @@ # -*- coding: UTF-8 -*- from datetime import datetime +from typing import List -class Part(): +class Part: content: str content_type: str -class Attachment(): +class Attachment: filename: str content: str content_type: str -class Email(): +class Email: id: int encoding: str date: datetime from_addr: str to_addr: str subject: str - parts: list[Part] - attachments: list[Attachment] + parts: List[Part] + attachments: List[Attachment] plain_text_content: str From 03e5d2dcea20529e88626974bb74eb614ee13b87 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 19:27:49 +0100 Subject: [PATCH 326/586] add mypy tool --- poetry.lock | 59 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + stacosys/interface/scheduler.py | 38 ++++++++++++++++++--- stacosys/model/email.py | 11 +++--- 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 997c104..82d1597 100644 --- a/poetry.lock +++ b/poetry.lock @@ -210,6 +210,30 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "19.2" @@ -394,6 +418,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "tzlocal" version = "2.0.0" @@ -457,7 +489,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "6a714b61f2ae04be8377aa2fb513c09bdae22cb71685b4b7ddfed843bfd9ff08" +content-hash = "f3a87d43b25f363c4ef34c8e8aa8b16aadb975c204d34234babbe0ee33038a02" [metadata.files] appdirs = [ @@ -562,6 +594,26 @@ more-itertools = [ {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, ] +mypy = [ + {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, + {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, + {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, + {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, + {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, + {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, + {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, + {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, + {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, + {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, + {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, + {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, + {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, + {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] packaging = [ {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, @@ -680,6 +732,11 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] tzlocal = [ {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, diff --git a/pyproject.toml b/pyproject.toml index 99ae291..a95099d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ tinydb = "^4.3.0" pytest = "^5.2" black = {version = "^19.10b0", allow-prereleases = true} rope = "^0.16.0" +mypy = "^0.790" [build-system] requires = ["poetry>=0.12"] diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 832a0d6..33ea3ef 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -7,11 +7,21 @@ from stacosys.interface import app class JobConfig(object): - JOBS = [] + JOBS: list = [] SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} - def __init__(self, imap_polling_seconds, new_comment_polling_seconds, lang, site_name, site_token, site_admin_email, mailer, rss): + def __init__( + self, + imap_polling_seconds, + new_comment_polling_seconds, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, + ): self.JOBS = [ { "id": "fetch_mail", @@ -30,8 +40,28 @@ class JobConfig(object): ] -def configure(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss): - app.config.from_object(JobConfig(imap_polling, comment_polling, lang, site_name, site_token, site_admin_email, mailer, rss)) +def configure( + imap_polling, + comment_polling, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, +): + app.config.from_object( + JobConfig( + imap_polling, + comment_polling, + lang, + site_name, + site_token, + site_admin_email, + mailer, + rss, + ) + ) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() diff --git a/stacosys/model/email.py b/stacosys/model/email.py index c3feb34..4d0b081 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -2,26 +2,27 @@ # -*- coding: UTF-8 -*- from datetime import datetime +from typing import List -class Part(): +class Part: content: str content_type: str -class Attachment(): +class Attachment: filename: str content: str content_type: str -class Email(): +class Email: id: int encoding: str date: datetime from_addr: str to_addr: str subject: str - parts: list[Part] - attachments: list[Attachment] + parts: List[Part] + attachments: List[Attachment] plain_text_content: str From 30de8d56f72caa2d4d55d42fb6dc20b4a1c91f3c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:32:58 +0100 Subject: [PATCH 327/586] ignore sqlite files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5ee1fc..7c87c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ stacosys/lib64 .vscode/ .pytest_cache/ workspace.code-workspace +*.sqlite From b5992560e853830217f5f195101f8444bc424268 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 19 Jan 2021 19:32:58 +0100 Subject: [PATCH 328/586] ignore sqlite files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5ee1fc..7c87c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ stacosys/lib64 .vscode/ .pytest_cache/ workspace.code-workspace +*.sqlite From 98c81d584d568a8b9cd634b8c4450a5ccd8d3c35 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 20 Jan 2021 19:40:21 +0100 Subject: [PATCH 329/586] version 2.0-b1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a95099d..a0e41eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "1.1.0" +version = "2.0-b1" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From e2ce3fa7978ef1d6fbcf35248c10711a366bc295 Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 20 Jan 2021 19:40:21 +0100 Subject: [PATCH 330/586] version 2.0-b1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a95099d..a0e41eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "1.1.0" +version = "2.0-b1" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From b4c5743ce8a9952af27a1fc326a95e608d7be8dd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 23 Jan 2021 18:04:52 +0100 Subject: [PATCH 331/586] python 3.9 required --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a0e41eb..194d984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Yax"] readme = "README.md" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.9" apscheduler = "^3.6.3" flask = "^1.1.1" peewee = "^3.13.1" From f52eae82ab3b4ecf025f023ce114163b589bfff6 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 23 Jan 2021 18:04:52 +0100 Subject: [PATCH 332/586] python 3.9 required --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a0e41eb..194d984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Yax"] readme = "README.md" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.9" apscheduler = "^3.6.3" flask = "^1.1.1" peewee = "^3.13.1" From 51cfdbe2100df8e756fd35fd3e80d329995d3116 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 23 Jan 2021 18:06:41 +0100 Subject: [PATCH 333/586] version 2.0 beta 2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 194d984..4f4726e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0-b1" +version = "2.0b2" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From d6f7cdabfe73095611ba1cf8c2d6109f2fc7ef58 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 23 Jan 2021 18:06:41 +0100 Subject: [PATCH 334/586] version 2.0 beta 2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 194d984..4f4726e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0-b1" +version = "2.0b2" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From 542a923cbe30684dcabbec75804aa3bd7969e570 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 24 Jan 2021 18:23:20 +0100 Subject: [PATCH 335/586] upgrade libs and follow linter recommandations --- .gitignore | 1 + config.ini | 2 +- poetry.lock | 476 ++++++++++++++++++++----------------- pyproject.toml | 12 +- run.py | 23 +- stacosys/__init__.py | 2 +- stacosys/conf/config.py | 3 +- stacosys/core/cron.py | 8 +- stacosys/core/database.py | 5 +- stacosys/core/imap.py | 30 ++- stacosys/core/mailer.py | 11 +- stacosys/core/rss.py | 8 +- stacosys/interface/api.py | 4 +- stacosys/interface/form.py | 2 +- stacosys/model/comment.py | 2 - tests/test_config.py | 12 +- tests/test_stacosys.py | 2 +- tests/test_templater.py | 1 - 18 files changed, 321 insertions(+), 283 deletions(-) diff --git a/.gitignore b/.gitignore index 7c87c6d..991826a 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ stacosys/lib64 .pytest_cache/ workspace.code-workspace *.sqlite +config-server.ini diff --git a/config.ini b/config.ini index 9a7cfab..f4ba682 100755 --- a/config.ini +++ b/config.ini @@ -2,7 +2,7 @@ ; Default configuration [main] lang = fr -db_url = db.sqlite +db_sqlite_file = db.sqlite db_backup_json_file = db.json newcomment_polling = 60 diff --git a/poetry.lock b/poetry.lock index 82d1597..b5aaefe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "appdirs" -version = "1.4.3" +version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -8,33 +8,33 @@ python-versions = "*" [[package]] name = "apscheduler" -version = "3.6.3" +version = "3.7.0" description = "In-process task scheduler with Cron-like capabilities" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=1.2" +tzlocal = ">=2.0,<3.0" [package.extras] asyncio = ["trollius"] doc = ["sphinx", "sphinx-rtd-theme"] gevent = ["gevent"] -mongodb = ["pymongo (>=2.8)"] +mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +testing = ["pytest (<6)", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] name = "atomicwrites" -version = "1.3.0" +version = "1.4.0" description = "Atomic file writes." category = "dev" optional = false @@ -42,21 +42,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "19.3.0" +version = "20.3.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "black" -version = "19.10b0" +version = "20.8b1" description = "The uncompromising code formatter." category = "dev" optional = false @@ -64,19 +64,21 @@ python-versions = ">=3.6" [package.dependencies] appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" +regex = ">=2020.1.8" +toml = ">=0.10.1" typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" [package.extras] +colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2019.11.28" +version = "2020.12.5" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -84,31 +86,56 @@ python-versions = "*" [[package]] name = "chardet" -version = "3.0.4" +version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "click" -version = "7.0" +version = "7.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "colorama" -version = "0.4.3" +version = "0.4.4" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "flake8-black" +version = "0.2.1" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +black = "*" +flake8 = ">=3.0.0" + [[package]] name = "flask" -version = "1.1.1" +version = "1.1.2" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -140,26 +167,19 @@ python-dateutil = ">=2.4.2" [[package]] name = "idna" -version = "2.8" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "importlib-metadata" -version = "1.3.0" -description = "Read metadata from Python packages" +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "importlib-resources"] +python-versions = "*" [[package]] name = "itsdangerous" @@ -171,11 +191,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "jinja2" -version = "2.10.3" +version = "2.11.2" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] MarkupSafe = ">=0.23" @@ -185,11 +205,11 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "markdown" -version = "3.1.1" +version = "3.3.3" description = "Python implementation of Markdown." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.6" [package.extras] testing = ["coverage", "pyyaml"] @@ -203,12 +223,12 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] -name = "more-itertools" -version = "8.0.2" -description = "More routines for operating on iterables, beyond itertools" +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = "*" [[package]] name = "mypy" @@ -236,7 +256,7 @@ python-versions = "*" [[package]] name = "packaging" -version = "19.2" +version = "20.8" description = "Core utilities for Python packages" category = "dev" optional = false @@ -244,11 +264,10 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] name = "pathspec" -version = "0.7.0" +version = "0.8.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -256,7 +275,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "peewee" -version = "3.13.1" +version = "3.14.0" description = "a little orm" category = "main" optional = false @@ -270,9 +289,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] @@ -286,15 +302,31 @@ python-versions = "*" [[package]] name = "py" -version = "1.8.1" +version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pyparsing" -version = "2.4.6" +version = "2.4.7" description = "Python parsing module" category = "dev" optional = false @@ -310,22 +342,21 @@ python-versions = "*" [[package]] name = "pytest" -version = "5.3.2" +version = "6.2.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" +attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -more-itertools = ">=4.0.0" +iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] @@ -343,7 +374,7 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2019.3" +version = "2020.5" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -351,7 +382,7 @@ python-versions = "*" [[package]] name = "regex" -version = "2020.2.20" +version = "2020.11.13" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -359,7 +390,7 @@ python-versions = "*" [[package]] name = "requests" -version = "2.22.0" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -367,12 +398,12 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<3.1.0" -idna = ">=2.5,<2.9" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] @@ -388,11 +419,11 @@ dev = ["pytest"] [[package]] name = "six" -version = "1.13.0" +version = "1.15.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tinydb" @@ -404,15 +435,15 @@ python-versions = ">=3.5,<4.0" [[package]] name = "toml" -version = "0.10.0" +version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typed-ast" -version = "1.4.1" +version = "1.4.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -428,7 +459,7 @@ python-versions = "*" [[package]] name = "tzlocal" -version = "2.0.0" +version = "2.1" description = "tzinfo object for the local timezone" category = "main" optional = false @@ -439,121 +470,103 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.25.7" +version = "1.26.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "wcwidth" -version = "0.1.7" -description = "Measures number of Terminal column cells of wide-character codes" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "werkzeug" -version = "0.16.0" +version = "1.0.1" description = "The comprehensive WSGI web application library." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] -termcolor = ["termcolor"] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] -[[package]] -name = "zipp" -version = "0.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -more-itertools = "*" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "contextlib2", "unittest2"] - [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "f3a87d43b25f363c4ef34c8e8aa8b16aadb975c204d34234babbe0ee33038a02" +python-versions = "^3.9" +content-hash = "e48d69ed299d1e475a383b7df429d886a1f0e4ac5fa156812a2dc013aa1dce59" [metadata.files] appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] apscheduler = [ - {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, - {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, + {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, + {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, ] atomicwrites = [ - {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, - {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, - {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] click = [ - {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, - {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +flake8-black = [ + {file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"}, ] flask = [ - {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, - {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, + {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, + {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, ] flask-apscheduler = [ {file = "Flask-APScheduler-1.11.0.tar.gz", hash = "sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0"}, ] idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] -importlib-metadata = [ - {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"}, - {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"}, +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ - {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, - {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] markdown = [ - {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, - {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, + {file = "Markdown-3.3.3-py3-none-any.whl", hash = "sha256:c109c15b7dc20a9ac454c9e6025927d44460b85bd039da028d85e2b6d0bcc328"}, + {file = "Markdown-3.3.3.tar.gz", hash = "sha256:5d9f2b5ca24bc4c7a390d22323ca4bad200368612b5aaa7796babf971d2b2f18"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -590,9 +603,9 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] -more-itertools = [ - {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, - {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mypy = [ {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, @@ -615,15 +628,15 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, - {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, + {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, + {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, ] pathspec = [ - {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, - {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] peewee = [ - {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, + {file = "peewee-3.14.0.tar.gz", hash = "sha256:59c5ef43877029b9133d87001dcc425525de231d1f983cece8828197fb4b84fa"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -633,54 +646,82 @@ profig = [ {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, ] py = [ - {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, - {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pyparsing = [ - {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, - {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"}, - {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"}, + {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"}, + {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, + {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, + {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, ] regex = [ - {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, - {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, - {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, - {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, - {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, - {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, - {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, - {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, - {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, ] requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] rope = [ {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, @@ -688,49 +729,48 @@ rope = [ {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, ] six = [ - {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, - {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] tinydb = [ {file = "tinydb-4.3.0-py3-none-any.whl", hash = "sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"}, {file = "tinydb-4.3.0.tar.gz", hash = "sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303"}, ] toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, - {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, - {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, + {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, + {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, + {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, + {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, + {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, + {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, + {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, + {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, + {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, ] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, @@ -738,22 +778,14 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] tzlocal = [ - {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, - {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, - {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, -] -wcwidth = [ - {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, - {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] werkzeug = [ - {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, - {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, -] -zipp = [ - {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, - {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] diff --git a/pyproject.toml b/pyproject.toml index 4f4726e..fea1773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,24 +4,26 @@ version = "2.0b2" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" +include = ["run.py"] [tool.poetry.dependencies] python = "^3.9" apscheduler = "^3.6.3" -flask = "^1.1.1" -peewee = "^3.13.1" pyrss2gen = "^1.1" profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" -requests = "^2.22.0" tinydb = "^4.3.0" +Flask = "^1.1.2" +peewee = "^3.14.0" +requests = "^2.25.1" [tool.poetry.dev-dependencies] -pytest = "^5.2" -black = {version = "^19.10b0", allow-prereleases = true} rope = "^0.16.0" mypy = "^0.790" +flake8-black = "^0.2.1" +black = "^20.8b1" +pytest = "^6.2.1" [build-system] requires = ["poetry>=0.12"] diff --git a/run.py b/run.py index 51565cd..754dee6 100644 --- a/run.py +++ b/run.py @@ -5,15 +5,12 @@ import sys import os import argparse import logging -from flask import Flask from stacosys.conf.config import Config, ConfigParameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer from stacosys.interface import app -from stacosys.interface import api -from stacosys.interface import form from stacosys.interface import scheduler @@ -33,17 +30,29 @@ def configure_logging(level): def stacosys_server(config_pathname): - conf = Config.load(config_pathname) - # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING logging.getLogger("apscheduler.executors").level = logging.WARNING + # check config file exists + if not os.path.isfile(config_pathname): + logger.error(f"Configuration file '{config_pathname}' not found.") + sys.exit(1) + + # initialize config + conf = Config.load(config_pathname) + + # check database file exists (prevents from creating a fresh db) + db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) + if not os.path.isfile(db_pathname): + logger.error(f"Database file '{db_pathname}' not found.") + sys.exit(1) + # initialize database db = database.Database() - db.setup(conf.get(ConfigParameter.DB_URL)) + db.setup(db_pathname) logger.info("Start Stacosys application") @@ -53,7 +62,7 @@ def stacosys_server(config_pathname): conf.get(ConfigParameter.RSS_FILE), conf.get(ConfigParameter.RSS_PROTO), conf.get(ConfigParameter.SITE_NAME), - conf.get(ConfigParameter.SITE_URL) + conf.get(ConfigParameter.SITE_URL), ) rss.generate() diff --git a/stacosys/__init__.py b/stacosys/__init__.py index 3b3dacb..f2dc0e4 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = '2.0' +__version__ = "2.0" diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 5962f4c..a2571bd 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -6,7 +6,7 @@ import profig class ConfigParameter(Enum): - DB_URL = "main.db_url" + DB_SQLITE_FILE = "main.db_sqlite_file" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" COMMENT_POLLING = "main.newcomment_polling" @@ -62,4 +62,3 @@ class Config: def get_bool(self, key: ConfigParameter): return self._params[key.value].lower() in ("yes", "true") - diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 292d4e8..3f02c30 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -4,10 +4,7 @@ import logging import os import re -import time -from datetime import datetime -from stacosys.core import rss from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.email import Email @@ -39,9 +36,8 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): return # retrieve site and comment rows - try: - comment = Comment.get_by_id(comment_id) - except: + comment = Comment.get_by_id(comment_id) + if not comment: logger.warn("unknown comment %d" % comment_id) return True diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 1de6db4..45b89fb 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -2,8 +2,8 @@ # -*- coding: UTF-8 -*- import json -from peewee import DatabaseProxy, Model -from playhouse.db_url import connect, SqliteDatabase +from peewee import Model +from playhouse.db_url import SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB @@ -46,4 +46,3 @@ def _backup_db(db_file, Comment): for comment in Comment.select(): cc = _tojson_model(comment) table.insert(cc) - diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index dd29d71..cbd7105 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -7,6 +7,7 @@ import email import imaplib import logging import re +from email.message import Message from stacosys.model.email import Attachment, Email, Part @@ -50,7 +51,7 @@ class Mailbox(object): parts = [] attachments = [] - plain_text_content: 'no plain-text part' + plain_text_content = "no plain-text part" for part in raw_msg.walk(): if part.is_multipart(): continue @@ -73,18 +74,10 @@ class Mailbox(object): ) ) else: - part_item = {} - content = part.get_payload(decode=True) try: - charset = part.get_param("charset", None) - if charset: - content = to_utf8(content, charset) - elif type(content) == bytes: - content = content.decode("utf8") - except: - self.logger.exception() - # RFC 3676: remove automatic word-wrapping - content = content.replace(" \r\n", " ") + content = to_plain_text_content(part) + except Exception: + logging.exception("cannot extract content from mail part") parts.append( Part(content=content, content_type=part.get_content_type()) @@ -102,7 +95,7 @@ class Mailbox(object): subject=email_nonascii_to_uft8(raw_msg["Subject"]), parts=parts, attachments=attachments, - plain_text_content = plain_text_content + plain_text_content=plain_text_content, ) def delete_message(self, num): @@ -156,3 +149,14 @@ def email_nonascii_to_uft8(string): else: subject = subject + to_utf8(v, charset) return subject + + +def to_plain_text_content(part: Message) -> str: + content = part.get_payload(decode=True) + charset = part.get_param("charset", None) + if charset: + content = to_utf8(content, charset) + elif type(content) == bytes: + content = content.decode("utf8") + # RFC 3676: remove automatic word-wrapping + return content.replace(" \r\n", " ") diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index d4b48c1..b2b712e 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -1,16 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import json import logging import smtplib from email.mime.text import MIMEText -import requests - -from stacosys.conf import config from stacosys.core import imap -from stacosys.model.email import Email logger = logging.getLogger(__name__) @@ -56,7 +51,7 @@ class Mailer: count = mbox.get_count() for num in range(count): msgs.append(mbox.fetch_message(num + 1)) - except: + except Exception: logger.exception("fetch mail exception") return msgs @@ -76,7 +71,7 @@ class Mailer: s.login(self._smtp_login, self._smtp_password) s.send_message(msg) s.quit() - except: + except Exception: logger.exception("send mail exception") success = False return success @@ -85,5 +80,5 @@ class Mailer: try: with self._open_mailbox() as mbox: mbox.delete_message(id) - except: + except Exception: logger.exception("delete mail exception") diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 55076b5..79366af 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -7,14 +7,18 @@ from datetime import datetime import markdown import PyRSS2Gen -import stacosys.conf.config as config from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment class Rss: def __init__( - self, lang, rss_file, rss_proto, site_name, site_url, + self, + lang, + rss_file, + rss_proto, + site_name, + site_url, ): self._lang = lang self._rss_file = rss_file diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 3e5ef04..7b104e7 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -44,7 +44,7 @@ def query_comments(): comments.append(d) r = jsonify({"data": comments}) r.status_code = 200 - except: + except Exception: logger.warn("bad request") r = jsonify({"data": []}) r.status_code = 400 @@ -66,7 +66,7 @@ def get_comments_count(): ) r = jsonify({"count": count}) r.status_code = 200 - except: + except Exception: r = jsonify({"count": 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 5bb04c9..192b6a2 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -57,7 +57,7 @@ def new_form_comment(): ) comment.save() - except: + except Exception: logger.exception("new comment failure") abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 999f5a1..e586b25 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -1,11 +1,9 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from peewee import Model from peewee import CharField from peewee import TextField from peewee import DateTimeField -from peewee import ForeignKeyField from datetime import datetime from stacosys.core.database import BaseModel diff --git a/tests/test_config.py b/tests/test_config.py index b373587..3df0502 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,7 +4,7 @@ import pytest from stacosys.conf.config import Config, ConfigParameter -EXPECTED_DB_URL = "sqlite:///db.sqlite" +EXPECTED_DB_SQLITE_FILE = "db.sqlite" EXPECTED_HTTP_PORT = 8080 EXPECTED_IMAP_PORT = "5000" EXPECTED_IMAP_LOGIN = "user" @@ -13,7 +13,7 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): conf = Config() - conf.put(ConfigParameter.DB_URL, EXPECTED_DB_URL) + conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) conf.put(ConfigParameter.SMTP_STARTTLS, "yes") @@ -23,13 +23,13 @@ def conf(): def test_exists(conf): assert conf is not None - assert conf.exists(ConfigParameter.DB_URL) + assert conf.exists(ConfigParameter.DB_SQLITE_FILE) assert not conf.exists(ConfigParameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(ConfigParameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(ConfigParameter.DB_SQLITE_FILE) == EXPECTED_DB_SQLITE_FILE assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT assert conf.get(ConfigParameter.HTTP_HOST) is None assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT @@ -38,14 +38,14 @@ def test_get(conf): try: conf.get_int(ConfigParameter.HTTP_PORT) assert False - except: + except Exception: pass assert conf.get_bool(ConfigParameter.SMTP_STARTTLS) assert not conf.get_bool(ConfigParameter.IMAP_SSL) try: conf.get_bool(ConfigParameter.DB_URL) assert False - except: + except Exception: pass diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 3b5b8b1..94714f0 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '2.0' + assert __version__ == "2.0" diff --git a/tests/test_templater.py b/tests/test_templater.py index 6f5b40f..aad652b 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -2,7 +2,6 @@ # -*- coding: UTF-8 -*- import os -import pytest from stacosys.core.templater import Templater, Template From 7070e2a273ef5975d9722d3763dc0527dca25784 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 24 Jan 2021 18:23:20 +0100 Subject: [PATCH 336/586] upgrade libs and follow linter recommandations --- .gitignore | 1 + config.ini | 2 +- poetry.lock | 476 ++++++++++++++++++++----------------- pyproject.toml | 12 +- run.py | 23 +- stacosys/__init__.py | 2 +- stacosys/conf/config.py | 3 +- stacosys/core/cron.py | 8 +- stacosys/core/database.py | 5 +- stacosys/core/imap.py | 30 ++- stacosys/core/mailer.py | 11 +- stacosys/core/rss.py | 8 +- stacosys/interface/api.py | 4 +- stacosys/interface/form.py | 2 +- stacosys/model/comment.py | 2 - tests/test_config.py | 12 +- tests/test_stacosys.py | 2 +- tests/test_templater.py | 1 - 18 files changed, 321 insertions(+), 283 deletions(-) diff --git a/.gitignore b/.gitignore index 7c87c6d..991826a 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ stacosys/lib64 .pytest_cache/ workspace.code-workspace *.sqlite +config-server.ini diff --git a/config.ini b/config.ini index 9a7cfab..f4ba682 100755 --- a/config.ini +++ b/config.ini @@ -2,7 +2,7 @@ ; Default configuration [main] lang = fr -db_url = db.sqlite +db_sqlite_file = db.sqlite db_backup_json_file = db.json newcomment_polling = 60 diff --git a/poetry.lock b/poetry.lock index 82d1597..b5aaefe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "appdirs" -version = "1.4.3" +version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -8,33 +8,33 @@ python-versions = "*" [[package]] name = "apscheduler" -version = "3.6.3" +version = "3.7.0" description = "In-process task scheduler with Cron-like capabilities" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=1.2" +tzlocal = ">=2.0,<3.0" [package.extras] asyncio = ["trollius"] doc = ["sphinx", "sphinx-rtd-theme"] gevent = ["gevent"] -mongodb = ["pymongo (>=2.8)"] +mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +testing = ["pytest (<6)", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] name = "atomicwrites" -version = "1.3.0" +version = "1.4.0" description = "Atomic file writes." category = "dev" optional = false @@ -42,21 +42,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "19.3.0" +version = "20.3.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "black" -version = "19.10b0" +version = "20.8b1" description = "The uncompromising code formatter." category = "dev" optional = false @@ -64,19 +64,21 @@ python-versions = ">=3.6" [package.dependencies] appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" +regex = ">=2020.1.8" +toml = ">=0.10.1" typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" [package.extras] +colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2019.11.28" +version = "2020.12.5" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -84,31 +86,56 @@ python-versions = "*" [[package]] name = "chardet" -version = "3.0.4" +version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "click" -version = "7.0" +version = "7.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "colorama" -version = "0.4.3" +version = "0.4.4" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "flake8-black" +version = "0.2.1" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +black = "*" +flake8 = ">=3.0.0" + [[package]] name = "flask" -version = "1.1.1" +version = "1.1.2" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -140,26 +167,19 @@ python-dateutil = ">=2.4.2" [[package]] name = "idna" -version = "2.8" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "importlib-metadata" -version = "1.3.0" -description = "Read metadata from Python packages" +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "importlib-resources"] +python-versions = "*" [[package]] name = "itsdangerous" @@ -171,11 +191,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "jinja2" -version = "2.10.3" +version = "2.11.2" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] MarkupSafe = ">=0.23" @@ -185,11 +205,11 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "markdown" -version = "3.1.1" +version = "3.3.3" description = "Python implementation of Markdown." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.6" [package.extras] testing = ["coverage", "pyyaml"] @@ -203,12 +223,12 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] -name = "more-itertools" -version = "8.0.2" -description = "More routines for operating on iterables, beyond itertools" +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = "*" [[package]] name = "mypy" @@ -236,7 +256,7 @@ python-versions = "*" [[package]] name = "packaging" -version = "19.2" +version = "20.8" description = "Core utilities for Python packages" category = "dev" optional = false @@ -244,11 +264,10 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] name = "pathspec" -version = "0.7.0" +version = "0.8.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -256,7 +275,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "peewee" -version = "3.13.1" +version = "3.14.0" description = "a little orm" category = "main" optional = false @@ -270,9 +289,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] @@ -286,15 +302,31 @@ python-versions = "*" [[package]] name = "py" -version = "1.8.1" +version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pyparsing" -version = "2.4.6" +version = "2.4.7" description = "Python parsing module" category = "dev" optional = false @@ -310,22 +342,21 @@ python-versions = "*" [[package]] name = "pytest" -version = "5.3.2" +version = "6.2.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" +attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -more-itertools = ">=4.0.0" +iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] @@ -343,7 +374,7 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2019.3" +version = "2020.5" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -351,7 +382,7 @@ python-versions = "*" [[package]] name = "regex" -version = "2020.2.20" +version = "2020.11.13" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -359,7 +390,7 @@ python-versions = "*" [[package]] name = "requests" -version = "2.22.0" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -367,12 +398,12 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<3.1.0" -idna = ">=2.5,<2.9" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] @@ -388,11 +419,11 @@ dev = ["pytest"] [[package]] name = "six" -version = "1.13.0" +version = "1.15.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tinydb" @@ -404,15 +435,15 @@ python-versions = ">=3.5,<4.0" [[package]] name = "toml" -version = "0.10.0" +version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typed-ast" -version = "1.4.1" +version = "1.4.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -428,7 +459,7 @@ python-versions = "*" [[package]] name = "tzlocal" -version = "2.0.0" +version = "2.1" description = "tzinfo object for the local timezone" category = "main" optional = false @@ -439,121 +470,103 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.25.7" +version = "1.26.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "wcwidth" -version = "0.1.7" -description = "Measures number of Terminal column cells of wide-character codes" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "werkzeug" -version = "0.16.0" +version = "1.0.1" description = "The comprehensive WSGI web application library." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] -termcolor = ["termcolor"] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] -[[package]] -name = "zipp" -version = "0.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -more-itertools = "*" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "contextlib2", "unittest2"] - [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "f3a87d43b25f363c4ef34c8e8aa8b16aadb975c204d34234babbe0ee33038a02" +python-versions = "^3.9" +content-hash = "e48d69ed299d1e475a383b7df429d886a1f0e4ac5fa156812a2dc013aa1dce59" [metadata.files] appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] apscheduler = [ - {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, - {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, + {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, + {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, ] atomicwrites = [ - {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, - {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, - {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] click = [ - {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, - {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +flake8-black = [ + {file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"}, ] flask = [ - {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, - {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, + {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, + {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, ] flask-apscheduler = [ {file = "Flask-APScheduler-1.11.0.tar.gz", hash = "sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0"}, ] idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] -importlib-metadata = [ - {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"}, - {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"}, +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ - {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, - {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] markdown = [ - {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, - {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, + {file = "Markdown-3.3.3-py3-none-any.whl", hash = "sha256:c109c15b7dc20a9ac454c9e6025927d44460b85bd039da028d85e2b6d0bcc328"}, + {file = "Markdown-3.3.3.tar.gz", hash = "sha256:5d9f2b5ca24bc4c7a390d22323ca4bad200368612b5aaa7796babf971d2b2f18"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -590,9 +603,9 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] -more-itertools = [ - {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, - {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mypy = [ {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, @@ -615,15 +628,15 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, - {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, + {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, + {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, ] pathspec = [ - {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, - {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] peewee = [ - {file = "peewee-3.13.1.tar.gz", hash = "sha256:9492af4d1f8e18a7fa0e930960315b38931286ea0f1659bbd5503456cffdacde"}, + {file = "peewee-3.14.0.tar.gz", hash = "sha256:59c5ef43877029b9133d87001dcc425525de231d1f983cece8828197fb4b84fa"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -633,54 +646,82 @@ profig = [ {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, ] py = [ - {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, - {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pyparsing = [ - {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, - {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"}, - {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"}, + {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"}, + {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, + {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, + {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, ] regex = [ - {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, - {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, - {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, - {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, - {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, - {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, - {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, - {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, - {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, - {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, - {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, - {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, ] requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] rope = [ {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, @@ -688,49 +729,48 @@ rope = [ {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, ] six = [ - {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, - {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] tinydb = [ {file = "tinydb-4.3.0-py3-none-any.whl", hash = "sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"}, {file = "tinydb-4.3.0.tar.gz", hash = "sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303"}, ] toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, - {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, - {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, + {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, + {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, + {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, + {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, + {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, + {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, + {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, + {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, + {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, ] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, @@ -738,22 +778,14 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] tzlocal = [ - {file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"}, - {file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"}, + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, - {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, -] -wcwidth = [ - {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, - {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] werkzeug = [ - {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, - {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, -] -zipp = [ - {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, - {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] diff --git a/pyproject.toml b/pyproject.toml index 4f4726e..fea1773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,24 +4,26 @@ version = "2.0b2" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" +include = ["run.py"] [tool.poetry.dependencies] python = "^3.9" apscheduler = "^3.6.3" -flask = "^1.1.1" -peewee = "^3.13.1" pyrss2gen = "^1.1" profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" -requests = "^2.22.0" tinydb = "^4.3.0" +Flask = "^1.1.2" +peewee = "^3.14.0" +requests = "^2.25.1" [tool.poetry.dev-dependencies] -pytest = "^5.2" -black = {version = "^19.10b0", allow-prereleases = true} rope = "^0.16.0" mypy = "^0.790" +flake8-black = "^0.2.1" +black = "^20.8b1" +pytest = "^6.2.1" [build-system] requires = ["poetry>=0.12"] diff --git a/run.py b/run.py index 51565cd..754dee6 100644 --- a/run.py +++ b/run.py @@ -5,15 +5,12 @@ import sys import os import argparse import logging -from flask import Flask from stacosys.conf.config import Config, ConfigParameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer from stacosys.interface import app -from stacosys.interface import api -from stacosys.interface import form from stacosys.interface import scheduler @@ -33,17 +30,29 @@ def configure_logging(level): def stacosys_server(config_pathname): - conf = Config.load(config_pathname) - # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING logging.getLogger("apscheduler.executors").level = logging.WARNING + # check config file exists + if not os.path.isfile(config_pathname): + logger.error(f"Configuration file '{config_pathname}' not found.") + sys.exit(1) + + # initialize config + conf = Config.load(config_pathname) + + # check database file exists (prevents from creating a fresh db) + db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) + if not os.path.isfile(db_pathname): + logger.error(f"Database file '{db_pathname}' not found.") + sys.exit(1) + # initialize database db = database.Database() - db.setup(conf.get(ConfigParameter.DB_URL)) + db.setup(db_pathname) logger.info("Start Stacosys application") @@ -53,7 +62,7 @@ def stacosys_server(config_pathname): conf.get(ConfigParameter.RSS_FILE), conf.get(ConfigParameter.RSS_PROTO), conf.get(ConfigParameter.SITE_NAME), - conf.get(ConfigParameter.SITE_URL) + conf.get(ConfigParameter.SITE_URL), ) rss.generate() diff --git a/stacosys/__init__.py b/stacosys/__init__.py index 3b3dacb..f2dc0e4 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = '2.0' +__version__ = "2.0" diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 5962f4c..a2571bd 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -6,7 +6,7 @@ import profig class ConfigParameter(Enum): - DB_URL = "main.db_url" + DB_SQLITE_FILE = "main.db_sqlite_file" DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" COMMENT_POLLING = "main.newcomment_polling" @@ -62,4 +62,3 @@ class Config: def get_bool(self, key: ConfigParameter): return self._params[key.value].lower() in ("yes", "true") - diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 292d4e8..3f02c30 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -4,10 +4,7 @@ import logging import os import re -import time -from datetime import datetime -from stacosys.core import rss from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.email import Email @@ -39,9 +36,8 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): return # retrieve site and comment rows - try: - comment = Comment.get_by_id(comment_id) - except: + comment = Comment.get_by_id(comment_id) + if not comment: logger.warn("unknown comment %d" % comment_id) return True diff --git a/stacosys/core/database.py b/stacosys/core/database.py index 1de6db4..45b89fb 100644 --- a/stacosys/core/database.py +++ b/stacosys/core/database.py @@ -2,8 +2,8 @@ # -*- coding: UTF-8 -*- import json -from peewee import DatabaseProxy, Model -from playhouse.db_url import connect, SqliteDatabase +from peewee import Model +from playhouse.db_url import SqliteDatabase from playhouse.shortcuts import model_to_dict from tinydb import TinyDB @@ -46,4 +46,3 @@ def _backup_db(db_file, Comment): for comment in Comment.select(): cc = _tojson_model(comment) table.insert(cc) - diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index dd29d71..cbd7105 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -7,6 +7,7 @@ import email import imaplib import logging import re +from email.message import Message from stacosys.model.email import Attachment, Email, Part @@ -50,7 +51,7 @@ class Mailbox(object): parts = [] attachments = [] - plain_text_content: 'no plain-text part' + plain_text_content = "no plain-text part" for part in raw_msg.walk(): if part.is_multipart(): continue @@ -73,18 +74,10 @@ class Mailbox(object): ) ) else: - part_item = {} - content = part.get_payload(decode=True) try: - charset = part.get_param("charset", None) - if charset: - content = to_utf8(content, charset) - elif type(content) == bytes: - content = content.decode("utf8") - except: - self.logger.exception() - # RFC 3676: remove automatic word-wrapping - content = content.replace(" \r\n", " ") + content = to_plain_text_content(part) + except Exception: + logging.exception("cannot extract content from mail part") parts.append( Part(content=content, content_type=part.get_content_type()) @@ -102,7 +95,7 @@ class Mailbox(object): subject=email_nonascii_to_uft8(raw_msg["Subject"]), parts=parts, attachments=attachments, - plain_text_content = plain_text_content + plain_text_content=plain_text_content, ) def delete_message(self, num): @@ -156,3 +149,14 @@ def email_nonascii_to_uft8(string): else: subject = subject + to_utf8(v, charset) return subject + + +def to_plain_text_content(part: Message) -> str: + content = part.get_payload(decode=True) + charset = part.get_param("charset", None) + if charset: + content = to_utf8(content, charset) + elif type(content) == bytes: + content = content.decode("utf8") + # RFC 3676: remove automatic word-wrapping + return content.replace(" \r\n", " ") diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index d4b48c1..b2b712e 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -1,16 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import json import logging import smtplib from email.mime.text import MIMEText -import requests - -from stacosys.conf import config from stacosys.core import imap -from stacosys.model.email import Email logger = logging.getLogger(__name__) @@ -56,7 +51,7 @@ class Mailer: count = mbox.get_count() for num in range(count): msgs.append(mbox.fetch_message(num + 1)) - except: + except Exception: logger.exception("fetch mail exception") return msgs @@ -76,7 +71,7 @@ class Mailer: s.login(self._smtp_login, self._smtp_password) s.send_message(msg) s.quit() - except: + except Exception: logger.exception("send mail exception") success = False return success @@ -85,5 +80,5 @@ class Mailer: try: with self._open_mailbox() as mbox: mbox.delete_message(id) - except: + except Exception: logger.exception("delete mail exception") diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 55076b5..79366af 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -7,14 +7,18 @@ from datetime import datetime import markdown import PyRSS2Gen -import stacosys.conf.config as config from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment class Rss: def __init__( - self, lang, rss_file, rss_proto, site_name, site_url, + self, + lang, + rss_file, + rss_proto, + site_name, + site_url, ): self._lang = lang self._rss_file = rss_file diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 3e5ef04..7b104e7 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -44,7 +44,7 @@ def query_comments(): comments.append(d) r = jsonify({"data": comments}) r.status_code = 200 - except: + except Exception: logger.warn("bad request") r = jsonify({"data": []}) r.status_code = 400 @@ -66,7 +66,7 @@ def get_comments_count(): ) r = jsonify({"count": count}) r.status_code = 200 - except: + except Exception: r = jsonify({"count": 0}) r.status_code = 200 return r diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 5bb04c9..192b6a2 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -57,7 +57,7 @@ def new_form_comment(): ) comment.save() - except: + except Exception: logger.exception("new comment failure") abort(400) diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 999f5a1..e586b25 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -1,11 +1,9 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from peewee import Model from peewee import CharField from peewee import TextField from peewee import DateTimeField -from peewee import ForeignKeyField from datetime import datetime from stacosys.core.database import BaseModel diff --git a/tests/test_config.py b/tests/test_config.py index b373587..3df0502 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,7 +4,7 @@ import pytest from stacosys.conf.config import Config, ConfigParameter -EXPECTED_DB_URL = "sqlite:///db.sqlite" +EXPECTED_DB_SQLITE_FILE = "db.sqlite" EXPECTED_HTTP_PORT = 8080 EXPECTED_IMAP_PORT = "5000" EXPECTED_IMAP_LOGIN = "user" @@ -13,7 +13,7 @@ EXPECTED_IMAP_LOGIN = "user" @pytest.fixture def conf(): conf = Config() - conf.put(ConfigParameter.DB_URL, EXPECTED_DB_URL) + conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) conf.put(ConfigParameter.SMTP_STARTTLS, "yes") @@ -23,13 +23,13 @@ def conf(): def test_exists(conf): assert conf is not None - assert conf.exists(ConfigParameter.DB_URL) + assert conf.exists(ConfigParameter.DB_SQLITE_FILE) assert not conf.exists(ConfigParameter.IMAP_HOST) def test_get(conf): assert conf is not None - assert conf.get(ConfigParameter.DB_URL) == EXPECTED_DB_URL + assert conf.get(ConfigParameter.DB_SQLITE_FILE) == EXPECTED_DB_SQLITE_FILE assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT assert conf.get(ConfigParameter.HTTP_HOST) is None assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT @@ -38,14 +38,14 @@ def test_get(conf): try: conf.get_int(ConfigParameter.HTTP_PORT) assert False - except: + except Exception: pass assert conf.get_bool(ConfigParameter.SMTP_STARTTLS) assert not conf.get_bool(ConfigParameter.IMAP_SSL) try: conf.get_bool(ConfigParameter.DB_URL) assert False - except: + except Exception: pass diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 3b5b8b1..94714f0 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -2,4 +2,4 @@ from stacosys import __version__ def test_version(): - assert __version__ == '2.0' + assert __version__ == "2.0" diff --git a/tests/test_templater.py b/tests/test_templater.py index 6f5b40f..aad652b 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -2,7 +2,6 @@ # -*- coding: UTF-8 -*- import os -import pytest from stacosys.core.templater import Templater, Template From 59e020228ad7373f137e8b792577414e352abc0d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 28 Jan 2021 19:00:54 +0100 Subject: [PATCH 337/586] fix a couple of bugs --- config.ini | 1 + pyproject.toml | 2 +- run.py | 7 ++++++- stacosys/conf/config.py | 4 ++++ stacosys/core/cron.py | 11 +++++------ stacosys/core/mailer.py | 7 ++++++- stacosys/interface/__init__.py | 5 +++-- stacosys/model/email.py | 4 ++++ 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/config.ini b/config.ini index f4ba682..33e896a 100755 --- a/config.ini +++ b/config.ini @@ -31,6 +31,7 @@ password = MYPASSWORD [smtp] host = mail.gandi.net starttls = true +ssl = false port = 587 login = blog@mydomain.com password = MYPASSWORD diff --git a/pyproject.toml b/pyproject.toml index fea1773..93c2f93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0b2" +version = "2.0b3" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" diff --git a/run.py b/run.py index 754dee6..4ebef94 100644 --- a/run.py +++ b/run.py @@ -11,6 +11,8 @@ from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer from stacosys.interface import app +from stacosys.interface import api +from stacosys.interface import form from stacosys.interface import scheduler @@ -43,7 +45,8 @@ def stacosys_server(config_pathname): # initialize config conf = Config.load(config_pathname) - + logger.info(conf.__repr__()) + # check database file exists (prevents from creating a fresh db) db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) if not os.path.isfile(db_pathname): @@ -76,6 +79,7 @@ def stacosys_server(config_pathname): conf.get(ConfigParameter.SMTP_HOST), conf.get_int(ConfigParameter.SMTP_PORT), conf.get_bool(ConfigParameter.SMTP_STARTTLS), + conf.get_bool(ConfigParameter.SMTP_SSL), conf.get(ConfigParameter.SMTP_LOGIN), conf.get(ConfigParameter.SMTP_PASSWORD), ) @@ -94,6 +98,7 @@ def stacosys_server(config_pathname): # inject config parameters into flask app.config.update(SITE_TOKEN=conf.get(ConfigParameter.SITE_TOKEN)) + logger.info(f"start interfaces {api} {form}") # start Flask app.run( diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a2571bd..8b8c0a2 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -25,6 +25,7 @@ class ConfigParameter(Enum): IMAP_PASSWORD = "imap.password" SMTP_STARTTLS = "smtp.starttls" + SMTP_SSL = "smtp.ssl" SMTP_HOST = "smtp.host" SMTP_PORT = "smtp.port" SMTP_LOGIN = "smtp.login" @@ -62,3 +63,6 @@ class Config: def get_bool(self, key: ConfigParameter): return self._params[key.value].lower() in ("yes", "true") + + def __repr__(self): + return self._params.__repr__() \ No newline at end of file diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 3f02c30..ff498cc 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -8,6 +8,8 @@ import re from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.email import Email +from stacosys.core.rss import Rss +from stacosys.core.mailer import Mailer logger = logging.getLogger(__name__) @@ -16,14 +18,14 @@ template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) -def fetch_mail_answers(lang, mailer, rss, site_token): +def fetch_mail_answers(lang, mailer: Mailer, rss: Rss, site_token): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): if _reply_comment_email(lang, mailer, rss, msg, site_token): mailer.delete(msg.id) -def _reply_comment_email(lang, mailer, rss, email: Email, site_token): +def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_token): m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: @@ -64,7 +66,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): logger.info("commit comment: %d" % comment_id) # rebuild RSS - rss.generate_site(token) + rss.generate() # send approval confirmation email to admin new_email_body = templater.get_template(lang, Template.APPROVE_COMMENT).render( @@ -77,9 +79,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): - for comment in Comment.select().where(Comment.notified.is_null()): - comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -103,4 +103,3 @@ def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): comment.notify_site_admin() else: logger.warn("rescheduled. send mail failure " + subject) - diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index b2b712e..4a5a406 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -21,6 +21,7 @@ class Mailer: smtp_host, smtp_port, smtp_starttls, + smtp_ssl, smtp_login, smtp_password, ): @@ -32,6 +33,7 @@ class Mailer: self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_starttls = smtp_starttls + self._smtp_ssl = smtp_ssl self._smtp_login = smtp_login self._smtp_password = smtp_password @@ -65,7 +67,10 @@ class Mailer: success = True try: - s = smtplib.SMTP(self._smtp_host, self._smtp_port) + if self._smtp_ssl: + s = smtplib.SMTP_SSL(self._smtp_host, self._smtp_port) + else: + s = smtplib.SMTP(self._smtp_host, self._smtp_port) if self._smtp_starttls: s.starttls() s.login(self._smtp_login, self._smtp_password) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index b7714c3..1fab892 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from flask import Flask -app = Flask(__name__) \ No newline at end of file +from flask import Flask + +app = Flask(__name__) diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 4d0b081..e67fecf 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -1,21 +1,25 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +from dataclasses import dataclass from datetime import datetime from typing import List +@dataclass class Part: content: str content_type: str +@dataclass class Attachment: filename: str content: str content_type: str +@dataclass class Email: id: int encoding: str From 3a19db0b2d030019a1229e5e42c34941fbaff280 Mon Sep 17 00:00:00 2001 From: Yax Date: Thu, 28 Jan 2021 19:00:54 +0100 Subject: [PATCH 338/586] fix a couple of bugs --- config.ini | 1 + pyproject.toml | 2 +- run.py | 7 ++++++- stacosys/conf/config.py | 4 ++++ stacosys/core/cron.py | 11 +++++------ stacosys/core/mailer.py | 7 ++++++- stacosys/interface/__init__.py | 5 +++-- stacosys/model/email.py | 4 ++++ 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/config.ini b/config.ini index f4ba682..33e896a 100755 --- a/config.ini +++ b/config.ini @@ -31,6 +31,7 @@ password = MYPASSWORD [smtp] host = mail.gandi.net starttls = true +ssl = false port = 587 login = blog@mydomain.com password = MYPASSWORD diff --git a/pyproject.toml b/pyproject.toml index fea1773..93c2f93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0b2" +version = "2.0b3" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" diff --git a/run.py b/run.py index 754dee6..4ebef94 100644 --- a/run.py +++ b/run.py @@ -11,6 +11,8 @@ from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer from stacosys.interface import app +from stacosys.interface import api +from stacosys.interface import form from stacosys.interface import scheduler @@ -43,7 +45,8 @@ def stacosys_server(config_pathname): # initialize config conf = Config.load(config_pathname) - + logger.info(conf.__repr__()) + # check database file exists (prevents from creating a fresh db) db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) if not os.path.isfile(db_pathname): @@ -76,6 +79,7 @@ def stacosys_server(config_pathname): conf.get(ConfigParameter.SMTP_HOST), conf.get_int(ConfigParameter.SMTP_PORT), conf.get_bool(ConfigParameter.SMTP_STARTTLS), + conf.get_bool(ConfigParameter.SMTP_SSL), conf.get(ConfigParameter.SMTP_LOGIN), conf.get(ConfigParameter.SMTP_PASSWORD), ) @@ -94,6 +98,7 @@ def stacosys_server(config_pathname): # inject config parameters into flask app.config.update(SITE_TOKEN=conf.get(ConfigParameter.SITE_TOKEN)) + logger.info(f"start interfaces {api} {form}") # start Flask app.run( diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index a2571bd..8b8c0a2 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -25,6 +25,7 @@ class ConfigParameter(Enum): IMAP_PASSWORD = "imap.password" SMTP_STARTTLS = "smtp.starttls" + SMTP_SSL = "smtp.ssl" SMTP_HOST = "smtp.host" SMTP_PORT = "smtp.port" SMTP_LOGIN = "smtp.login" @@ -62,3 +63,6 @@ class Config: def get_bool(self, key: ConfigParameter): return self._params[key.value].lower() in ("yes", "true") + + def __repr__(self): + return self._params.__repr__() \ No newline at end of file diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 3f02c30..ff498cc 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -8,6 +8,8 @@ import re from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment from stacosys.model.email import Email +from stacosys.core.rss import Rss +from stacosys.core.mailer import Mailer logger = logging.getLogger(__name__) @@ -16,14 +18,14 @@ template_path = os.path.abspath(os.path.join(current_path, "../templates")) templater = Templater(template_path) -def fetch_mail_answers(lang, mailer, rss, site_token): +def fetch_mail_answers(lang, mailer: Mailer, rss: Rss, site_token): for msg in mailer.fetch(): if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): if _reply_comment_email(lang, mailer, rss, msg, site_token): mailer.delete(msg.id) -def _reply_comment_email(lang, mailer, rss, email: Email, site_token): +def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_token): m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: @@ -64,7 +66,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): logger.info("commit comment: %d" % comment_id) # rebuild RSS - rss.generate_site(token) + rss.generate() # send approval confirmation email to admin new_email_body = templater.get_template(lang, Template.APPROVE_COMMENT).render( @@ -77,9 +79,7 @@ def _reply_comment_email(lang, mailer, rss, email: Email, site_token): def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): - for comment in Comment.select().where(Comment.notified.is_null()): - comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -103,4 +103,3 @@ def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): comment.notify_site_admin() else: logger.warn("rescheduled. send mail failure " + subject) - diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index b2b712e..4a5a406 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -21,6 +21,7 @@ class Mailer: smtp_host, smtp_port, smtp_starttls, + smtp_ssl, smtp_login, smtp_password, ): @@ -32,6 +33,7 @@ class Mailer: self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_starttls = smtp_starttls + self._smtp_ssl = smtp_ssl self._smtp_login = smtp_login self._smtp_password = smtp_password @@ -65,7 +67,10 @@ class Mailer: success = True try: - s = smtplib.SMTP(self._smtp_host, self._smtp_port) + if self._smtp_ssl: + s = smtplib.SMTP_SSL(self._smtp_host, self._smtp_port) + else: + s = smtplib.SMTP(self._smtp_host, self._smtp_port) if self._smtp_starttls: s.starttls() s.login(self._smtp_login, self._smtp_password) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index b7714c3..1fab892 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from flask import Flask -app = Flask(__name__) \ No newline at end of file +from flask import Flask + +app = Flask(__name__) diff --git a/stacosys/model/email.py b/stacosys/model/email.py index 4d0b081..e67fecf 100644 --- a/stacosys/model/email.py +++ b/stacosys/model/email.py @@ -1,21 +1,25 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +from dataclasses import dataclass from datetime import datetime from typing import List +@dataclass class Part: content: str content_type: str +@dataclass class Attachment: filename: str content: str content_type: str +@dataclass class Email: id: int encoding: str From 3456562af983868886461479031714a83a64c588 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 31 Jan 2021 17:01:26 +0100 Subject: [PATCH 339/586] update README, licence GPL v2 upgraded to v3 --- LICENSE | 838 ++++++++++++++++++++++++++++++++++++++---------------- README.md | 25 +- 2 files changed, 599 insertions(+), 264 deletions(-) diff --git a/LICENSE b/LICENSE index 8cdb845..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,281 +1,622 @@ GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + Version 3, 29 June 2007 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to + 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 -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +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 make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. + 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 give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. +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. - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. + 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. - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. + 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. - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. + 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. - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + TERMS AND CONDITIONS - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". + 0. Definitions. -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. + "This License" refers to version 3 of the GNU General Public License. - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. + "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. - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: + 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) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. + A "covered work" means either the unmodified Program or a work based +on the Program. - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. + 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. - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) + 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. -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. + 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. -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. + 1. Source Code. -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. + 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. - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: + A "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. - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, + 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. - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, + 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. - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. + The Corresponding Source for a work in source code form is that +same work. -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. + 2. Basic Permissions. - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. + 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. - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. + 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. - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to + 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. - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or + 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 -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. +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. -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. + 13. Use with the GNU Affero General Public License. -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. + 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. -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. + 14. Revised Versions of this License. - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will + 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 a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. + 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. - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. + 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. - NO WARRANTY + 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. - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. + 15. Disclaimer of Warranty. - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. + 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 @@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least +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. - {description} - Copyright (C) {year} {fullname} + + Copyright (C) - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + 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, @@ -303,38 +644,31 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + You should have received a copy of the GNU General Public License + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. +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 your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This 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. + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index e04cad4..be29528 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ ## Stacosys -Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. +Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an humble alternative to comment hosting services like Disqus. Stacosys protects your readers's privacy. -Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an -universal way to discuss. You can answer from any device using an e-mail client. +Stacosys works with any static blog or even a simple HTML page. It uses e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy. E-mails are reliable and an universal way to communicate. You can answer from any device using an e-mail client. ### Features overview @@ -17,33 +16,35 @@ Here is the workflow: - Blog administrator can approve or drop the comment by replying to e-mail - Stacosys stores approved comment in its database. +Privacy concerns: only surname, gravatar id and comment itself are stored in DB. E-mail is requested in submission form (but optional) to resolve gravatar id and it it not sent to stacosys. + Stacosys is localized (english and french). ### Technically speaking, how does it work? -Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a form request allows to post a comment which is relayed to the administrator by e-mail. For this purpose a dedicated email is assigned to Stacosys. ### FAQ *How do you block spammers?* -- Current comment form is basic: no captcha support but a honey pot. Second defense barrier: admin can tag comment as SPAM and, for example, link stacosys log to fail2ban tool. + +- Current comment form is basic: no captcha support but a honey pot. *Which database is used?* -- Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. + +- SQLite. *Which technologies are used?* -- [Python](https://www.python.org) +- [Python 3.9](https://www.python.org) - [Flask](http://flask.pocoo.org) - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) ### Installation -Python 3.7 +Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases on GitHub](https://github.com/kianby/stacosys/releases) or the [Docker image](https://hub.docker.com/repository/docker/kianby/stacosys). -pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig requests +### Improvements -### Ways of improvement - -Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. +Stacosys fits my needs and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork the project and enhance the project if you need more features. From d1677bdfe09611349499a9a9926e2c261a997211 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 31 Jan 2021 17:06:48 +0100 Subject: [PATCH 340/586] markdown joke --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be29528..feaed5e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Stacosys is localized (english and french). Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a form request allows to post a comment which is relayed to the administrator by e-mail. For this purpose a dedicated email is assigned to Stacosys. -### FAQ + +### Little FAQ *How do you block spammers?* From 055cc61e58b1aedc15845c524a18ef6e69e2ac79 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 3 Feb 2021 18:31:35 +0100 Subject: [PATCH 341/586] upgrade dependencies --- poetry.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index b5aaefe..889f258 100644 --- a/poetry.lock +++ b/poetry.lock @@ -191,7 +191,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "jinja2" -version = "2.11.2" +version = "2.11.3" description = "A very fast and expressive template engine." category = "main" optional = false @@ -256,7 +256,7 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.8" +version = "20.9" description = "Core utilities for Python packages" category = "dev" optional = false @@ -342,7 +342,7 @@ python-versions = "*" [[package]] name = "pytest" -version = "6.2.1" +version = "6.2.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -374,7 +374,7 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2020.5" +version = "2021.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -470,7 +470,7 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.26.2" +version = "1.26.3" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -561,8 +561,8 @@ itsdangerous = [ {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ - {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, - {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] markdown = [ {file = "Markdown-3.3.3-py3-none-any.whl", hash = "sha256:c109c15b7dc20a9ac454c9e6025927d44460b85bd039da028d85e2b6d0bcc328"}, @@ -628,8 +628,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, - {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, @@ -665,16 +665,16 @@ pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"}, - {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"}, + {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, + {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] pytz = [ - {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, - {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] regex = [ {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, @@ -782,8 +782,8 @@ tzlocal = [ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, - {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, ] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, From bc2292b0e8c89d05e6b85e403e47509a728cc4da Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 3 Feb 2021 18:31:46 +0100 Subject: [PATCH 342/586] add global count --- stacosys/interface/api.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 7b104e7..5c6151a 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -59,11 +59,16 @@ def get_comments_count(): abort(401) url = request.args.get("url", "") - count = ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .count() - ) + if url: + count = ( + Comment.select(Comment) + .where((Comment.url == url) & (Comment.published.is_null(False))) + .count() + ) + else: + count = ( + Comment.select(Comment).where(Comment.published.is_null(False)).count() + ) r = jsonify({"count": count}) r.status_code = 200 except Exception: From 087d2f94424742e660a19b713d3ab282e2f5414d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 3 Feb 2021 18:34:41 +0100 Subject: [PATCH 343/586] 2.0 beta 4 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 93c2f93..e5a2251 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0b3" +version = "2.0b4" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From 359294e166e73eef1965fbe65af2a9cfefd6b014 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 8 Feb 2021 18:20:13 +0100 Subject: [PATCH 344/586] remove umbrella --- stacosys/interface/api.py | 88 ++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 53 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 5c6151a..a3a8dea 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -3,13 +3,13 @@ import logging from flask import abort, jsonify, request - from stacosys.interface import app from stacosys.model.comment import Comment logger = logging.getLogger(__name__) + @app.route("/ping", methods=["GET"]) def ping(): return "OK" @@ -17,61 +17,43 @@ def ping(): @app.route("/comments", methods=["GET"]) def query_comments(): - comments = [] - try: - token = request.args.get("token", "") - if token != app.config.get("SITE_TOKEN"): - abort(401) + token = request.args.get("token", "") + if token != app.config.get("SITE_TOKEN"): + abort(401) + url = request.args.get("url", "") - url = request.args.get("url", "") - - logger.info("retrieve comments for url %s" % (url)) - for comment in ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .order_by(+Comment.published) - ): - d = { - "author": comment.author_name, - "content": comment.content, - "avatar": comment.author_gravatar, - "date": comment.published.strftime("%Y-%m-%d %H:%M:%S"), - } - if comment.author_site: - d["site"] = comment.author_site - logger.debug(d) - comments.append(d) - r = jsonify({"data": comments}) - r.status_code = 200 - except Exception: - logger.warn("bad request") - r = jsonify({"data": []}) - r.status_code = 400 - return r + logger.info("retrieve comments for url %s" % (url)) + for comment in ( + Comment.select(Comment) + .where((Comment.url == url) & (Comment.published.is_null(False))) + .order_by(+Comment.published) + ): + d = { + "author": comment.author_name, + "content": comment.content, + "avatar": comment.author_gravatar, + "date": comment.published.strftime("%Y-%m-%d %H:%M:%S"), + } + if comment.author_site: + d["site"] = comment.author_site + logger.debug(d) + comments.append(d) + return jsonify({"data": comments}) @app.route("/comments/count", methods=["GET"]) def get_comments_count(): - try: - token = request.args.get("token", "") - if token != app.config.get("SITE_TOKEN"): - abort(401) - - url = request.args.get("url", "") - if url: - count = ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .count() - ) - else: - count = ( - Comment.select(Comment).where(Comment.published.is_null(False)).count() - ) - r = jsonify({"count": count}) - r.status_code = 200 - except Exception: - r = jsonify({"count": 0}) - r.status_code = 200 - return r + token = request.args.get("token", "") + if token != app.config.get("SITE_TOKEN"): + abort(401) + url = request.args.get("url", "") + if url: + count = ( + Comment.select(Comment) + .where((Comment.url == url) & (Comment.published.is_null(False))) + .count() + ) + else: + count = Comment.select(Comment).where(Comment.publishd.is_null(False)).count() + return jsonify({"count": count}) From 105dbb4313384aeead0dd8852e27c857aef5c599 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 8 Feb 2021 18:35:46 +0100 Subject: [PATCH 345/586] add mail error logger --- run.py | 9 +++++- stacosys/core/mailer.py | 65 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index 4ebef94..63c0f81 100644 --- a/run.py +++ b/run.py @@ -10,6 +10,7 @@ from stacosys.conf.config import Config, ConfigParameter from stacosys.core import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer +from stacosys.core.mailer import SSLSMTPHandler from stacosys.interface import app from stacosys.interface import api from stacosys.interface import form @@ -46,7 +47,7 @@ def stacosys_server(config_pathname): # initialize config conf = Config.load(config_pathname) logger.info(conf.__repr__()) - + # check database file exists (prevents from creating a fresh db) db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) if not os.path.isfile(db_pathname): @@ -82,8 +83,14 @@ def stacosys_server(config_pathname): conf.get_bool(ConfigParameter.SMTP_SSL), conf.get(ConfigParameter.SMTP_LOGIN), conf.get(ConfigParameter.SMTP_PASSWORD), + conf.get(ConfigParameter.SITE_ADMIN_EMAIL) ) + # configure mailer logger + mail_handler = mailer.get_error_handler() + logger.addHandler(mail_handler) + app.logger.addHandler(mail_handler) + # configure scheduler scheduler.configure( conf.get_int(ConfigParameter.IMAP_POLLING), diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 4a5a406..c8a2ffd 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -3,7 +3,10 @@ import logging import smtplib +import email.utils from email.mime.text import MIMEText +from email.message import EmailMessage +from logging.handlers import SMTPHandler from stacosys.core import imap @@ -24,6 +27,7 @@ class Mailer: smtp_ssl, smtp_login, smtp_password, + site_admin_email, ): self._imap_host = imap_host self._imap_port = imap_port @@ -36,6 +40,7 @@ class Mailer: self._smtp_ssl = smtp_ssl self._smtp_login = smtp_login self._smtp_password = smtp_password + self._site_admin_email = site_admin_email def _open_mailbox(self): return imap.Mailbox( @@ -70,10 +75,11 @@ class Mailer: if self._smtp_ssl: s = smtplib.SMTP_SSL(self._smtp_host, self._smtp_port) else: - s = smtplib.SMTP(self._smtp_host, self._smtp_port) + s = smtplib.SMTP(self._smtp_host, self._smtp_port) if self._smtp_starttls: s.starttls() - s.login(self._smtp_login, self._smtp_password) + if self._smtp_login: + s.login(self._smtp_login, self._smtp_password) s.send_message(msg) s.quit() except Exception: @@ -87,3 +93,58 @@ class Mailer: mbox.delete_message(id) except Exception: logger.exception("delete mail exception") + + def get_error_handler(self): + if self._smtp_ssl: + mail_handler = SSLSMTPHandler( + mailhost=( + self._smtp_host, + self._smtp_port, + ), + credentials=( + self._smtp_login, + self._smtp_password, + ), + fromaddr=self._smtp_login, + toaddrs=self._site_admin_email, + subject="Stacosys error", + ) + else: + mail_handler = SMTPHandler( + mailhost=( + self._smtp_host, + self._smtp_port, + ), + credentials=( + self._smtp_login, + self._smtp_password, + ), + fromaddr=self._smtp_login, + toaddrs=self._site_admin_email, + subject="Stacosys error", + ) + mail_handler.setLevel(logging.ERROR) + return mail_handler + + +class SSLSMTPHandler(SMTPHandler): + def emit(self, record): + """ + Emit a record. + + Format the record and send it to the specified addressees. + """ + try: + smtp = smtplib.SMTP_SSL(self.mailhost, self.mailport) + msg = EmailMessage() + msg["From"] = self.fromaddr + msg["To"] = ",".join(self.toaddrs) + msg["Subject"] = self.getSubject(record) + msg["Date"] = email.utils.localtime() + msg.set_content(self.format(record)) + if self.username: + smtp.login(self.username, self.password) + smtp.send_message(msg) + smtp.quit() + except Exception: + self.handleError(record) From a65206f59f4651643290ab2ac1fb91430d0dcb94 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:10:25 +0200 Subject: [PATCH 346/586] update dependencies --- poetry.lock | 206 ++++++++++++++++++++++++++-------------------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/poetry.lock b/poetry.lock index 889f258..ae47d43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,16 +110,16 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-black" @@ -154,14 +154,14 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-apscheduler" -version = "1.11.0" +version = "1.12.2" description = "Adds APScheduler support to Flask" category = "main" optional = false python-versions = "*" [package.dependencies] -apscheduler = ">=3.2.0" +apscheduler = ">=3.2.0,<4.0.0" flask = ">=0.10.1" python-dateutil = ">=2.4.2" @@ -205,7 +205,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "markdown" -version = "3.3.3" +version = "3.3.4" description = "Python implementation of Markdown." category = "main" optional = false @@ -275,7 +275,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "peewee" -version = "3.14.0" +version = "3.14.4" description = "a little orm" category = "main" optional = false @@ -310,7 +310,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -318,7 +318,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -342,7 +342,7 @@ python-versions = "*" [[package]] name = "pytest" -version = "6.2.2" +version = "6.2.3" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -382,7 +382,7 @@ python-versions = "*" [[package]] name = "regex" -version = "2020.11.13" +version = "2021.4.4" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -427,7 +427,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tinydb" -version = "4.3.0" +version = "4.4.0" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" optional = false @@ -443,7 +443,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typed-ast" -version = "1.4.2" +version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -470,16 +470,16 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.26.3" +version = "1.26.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "werkzeug" @@ -535,8 +535,8 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.1-py2.py3-none-any.whl", hash = "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a"}, + {file = "flake8-3.9.1.tar.gz", hash = "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378"}, ] flake8-black = [ {file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"}, @@ -546,7 +546,7 @@ flask = [ {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, ] flask-apscheduler = [ - {file = "Flask-APScheduler-1.11.0.tar.gz", hash = "sha256:7911d66e449f412d92a1a6c524217f44f4c40a5c92148c60d5189c6c402f87d0"}, + {file = "Flask-APScheduler-1.12.2.tar.gz", hash = "sha256:b9fe174b90d201d8beeba5522b023208f7bb6e2583fc02fea4be4bce5ee8f9e5"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -565,8 +565,8 @@ jinja2 = [ {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] markdown = [ - {file = "Markdown-3.3.3-py3-none-any.whl", hash = "sha256:c109c15b7dc20a9ac454c9e6025927d44460b85bd039da028d85e2b6d0bcc328"}, - {file = "Markdown-3.3.3.tar.gz", hash = "sha256:5d9f2b5ca24bc4c7a390d22323ca4bad200368612b5aaa7796babf971d2b2f18"}, + {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, + {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -636,7 +636,7 @@ pathspec = [ {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] peewee = [ - {file = "peewee-3.14.0.tar.gz", hash = "sha256:59c5ef43877029b9133d87001dcc425525de231d1f983cece8828197fb4b84fa"}, + {file = "peewee-3.14.4.tar.gz", hash = "sha256:9e356b327c2eaec6dd42ecea6f4ddded025793dba906a3d065a0452e726c51a2"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -650,12 +650,12 @@ py = [ {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -665,8 +665,8 @@ pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, - {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, + {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, + {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, @@ -677,47 +677,47 @@ pytz = [ {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] regex = [ - {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, - {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, - {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, - {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, - {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, - {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, - {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, - {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, - {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, - {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, - {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, - {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, - {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, ] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, @@ -733,44 +733,44 @@ six = [ {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] tinydb = [ - {file = "tinydb-4.3.0-py3-none-any.whl", hash = "sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"}, - {file = "tinydb-4.3.0.tar.gz", hash = "sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303"}, + {file = "tinydb-4.4.0-py3-none-any.whl", hash = "sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71"}, + {file = "tinydb-4.4.0.tar.gz", hash = "sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typed-ast = [ - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, - {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, - {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, - {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, - {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, - {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, - {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, - {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, - {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, - {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, @@ -782,8 +782,8 @@ tzlocal = [ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, ] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, From 994eca83e95487846e0a6f03b5216707f8f02c49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 01:19:00 +0000 Subject: [PATCH 347/586] Bump urllib3 from 1.26.4 to 1.26.5 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.4 to 1.26.5. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.4...1.26.5) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index ae47d43..1008193 100644 --- a/poetry.lock +++ b/poetry.lock @@ -470,16 +470,16 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.5" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] +brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "werkzeug" @@ -540,6 +540,7 @@ flake8 = [ ] flake8-black = [ {file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"}, + {file = "flake8_black-0.2.1-py3-none-any.whl", hash = "sha256:941514149cb8b489cb17a4bb1cf18d84375db3b34381bb018de83509437931a0"}, ] flask = [ {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, @@ -587,20 +588,39 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -782,8 +802,8 @@ tzlocal = [ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, + {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, ] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, From 1736303949916b564367f5430d356c6aea8f4d38 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Jun 2021 11:10:57 +0200 Subject: [PATCH 348/586] disable api security for local testing --- config.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.ini b/config.ini index 33e896a..ee0abe2 100755 --- a/config.ini +++ b/config.ini @@ -9,7 +9,7 @@ newcomment_polling = 60 [site] name = "My blog" url = http://blog.mydomain.com -token = aabbccddeeffgghhiijjkkllmm +token = admin_email = admin@mydomain.com [http] From 938fe0b4e9c950754aabd90c5638d59b75a55ebe Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 09:27:37 +0200 Subject: [PATCH 349/586] flask 2.0 --- .gitignore | 1 + poetry.lock | 359 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 3 files changed, 175 insertions(+), 187 deletions(-) diff --git a/.gitignore b/.gitignore index 991826a..b93037e 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,4 @@ stacosys/lib64 workspace.code-workspace *.sqlite config-server.ini +.idea/ diff --git a/poetry.lock b/poetry.lock index 1008193..0d42293 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,17 +42,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "black" @@ -78,39 +78,45 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "7.1.2" +version = "8.0.1" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "flake8" -version = "3.9.1" +version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false @@ -123,7 +129,7 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-black" -version = "0.2.1" +version = "0.2.3" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false @@ -132,24 +138,24 @@ python-versions = "*" [package.dependencies] black = "*" flake8 = ">=3.0.0" +toml = "*" [[package]] name = "flask" -version = "1.1.2" +version = "2.0.1" description = "A simple framework for building complex web applications." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -click = ">=5.1" -itsdangerous = ">=0.24" -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" +click = ">=7.1.2" +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" [package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] @@ -167,11 +173,11 @@ python-dateutil = ">=2.4.2" [[package]] name = "idna" -version = "2.10" +version = "3.2" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "iniconfig" @@ -183,25 +189,25 @@ python-versions = "*" [[package]] name = "itsdangerous" -version = "1.1.0" -description = "Various helpers to pass data to untrusted environments and back." +version = "2.0.1" +description = "Safely pass data to untrusted environments and back." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "jinja2" -version = "2.11.3" +version = "3.0.1" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -MarkupSafe = ">=0.23" +MarkupSafe = ">=2.0" [package.extras] -i18n = ["Babel (>=0.8)"] +i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" @@ -216,11 +222,11 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "1.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -256,11 +262,11 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.9" +version = "21.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2" @@ -342,7 +348,7 @@ python-versions = "*" [[package]] name = "pytest" -version = "6.2.3" +version = "6.2.4" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -363,7 +369,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" optional = false @@ -382,7 +388,7 @@ python-versions = "*" [[package]] name = "regex" -version = "2021.4.4" +version = "2021.7.6" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -390,21 +396,21 @@ python-versions = "*" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rope" @@ -419,7 +425,7 @@ dev = ["pytest"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false @@ -427,7 +433,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tinydb" -version = "4.4.0" +version = "4.5.0" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" optional = false @@ -451,7 +457,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false @@ -470,7 +476,7 @@ pytz = "*" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.6" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -483,20 +489,19 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "werkzeug" -version = "1.0.1" +version = "2.0.1" description = "The comprehensive WSGI web application library." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.extras] -dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e48d69ed299d1e475a383b7df429d886a1f0e4ac5fa156812a2dc013aa1dce59" +content-hash = "c2c76edb4fcae80581572bcb9699f0b8b0193cbcc5e08a4b6f431c15f09236e7" [metadata.files] appdirs = [ @@ -512,116 +517,98 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"}, + {file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] flake8 = [ - {file = "flake8-3.9.1-py2.py3-none-any.whl", hash = "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a"}, - {file = "flake8-3.9.1.tar.gz", hash = "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378"}, + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] flake8-black = [ - {file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"}, - {file = "flake8_black-0.2.1-py3-none-any.whl", hash = "sha256:941514149cb8b489cb17a4bb1cf18d84375db3b34381bb018de83509437931a0"}, + {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, + {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, ] flask = [ - {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, - {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, + {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"}, + {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"}, ] flask-apscheduler = [ {file = "Flask-APScheduler-1.12.2.tar.gz", hash = "sha256:b9fe174b90d201d8beeba5522b023208f7bb6e2583fc02fea4be4bce5ee8f9e5"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] itsdangerous = [ - {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, - {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, + {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, + {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, ] jinja2 = [ - {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, - {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, ] markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -648,8 +635,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, @@ -685,63 +672,63 @@ pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, - {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, + {file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"}, + {file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"}, + {file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"}, + {file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"}, + {file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"}, + {file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"}, + {file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"}, + {file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"}, + {file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"}, + {file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"}, + {file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"}, + {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, + {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] rope = [ {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, @@ -749,12 +736,12 @@ rope = [ {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] tinydb = [ - {file = "tinydb-4.4.0-py3-none-any.whl", hash = "sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71"}, - {file = "tinydb-4.4.0.tar.gz", hash = "sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a"}, + {file = "tinydb-4.5.0-py3-none-any.whl", hash = "sha256:ab2669b88ba1e1b3e1bd6da1a1e3ee284fde6fbf327fb16a206ac3954915f37f"}, + {file = "tinydb-4.5.0.tar.gz", hash = "sha256:d287cd092f19a2b8553d0a6018f172c351268fb8619898eb87633d9e2c036344"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, @@ -793,19 +780,19 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] tzlocal = [ {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, ] werkzeug = [ - {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, - {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, + {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, + {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, ] diff --git a/pyproject.toml b/pyproject.toml index e5a2251..e3bd86c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" tinydb = "^4.3.0" -Flask = "^1.1.2" +Flask = "^2.0.1" peewee = "^3.14.0" requests = "^2.25.1" From bf1447a3a939cb86156ac1938436637bf4e55e91 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 09:44:58 +0200 Subject: [PATCH 350/586] add dao --- run.py | 3 +-- stacosys/core/cron.py | 25 +++++++++++++------------ stacosys/db/__init__.py | 0 stacosys/db/dao.py | 18 ++++++++++++++++++ stacosys/{core => db}/database.py | 0 stacosys/model/comment.py | 10 +--------- 6 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 stacosys/db/__init__.py create mode 100644 stacosys/db/dao.py rename stacosys/{core => db}/database.py (100%) diff --git a/run.py b/run.py index 63c0f81..3496f48 100644 --- a/run.py +++ b/run.py @@ -7,10 +7,9 @@ import argparse import logging from stacosys.conf.config import Config, ConfigParameter -from stacosys.core import database +from stacosys.db import database from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer -from stacosys.core.mailer import SSLSMTPHandler from stacosys.interface import app from stacosys.interface import api from stacosys.interface import form diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index ff498cc..0cd59e1 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -10,6 +10,7 @@ from stacosys.model.comment import Comment from stacosys.model.email import Email from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer +from stacosys.db import dao logger = logging.getLogger(__name__) @@ -29,40 +30,40 @@ def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_toke m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) if not m: - logger.warn("ignore corrupted email. No token %s" % email.subject) + logger.warning("ignore corrupted email. No token %s" % email.subject) return comment_id = int(m.group(1)) token = m.group(2) if token != site_token: - logger.warn("ignore corrupted email. Unknown token %d" % comment_id) + logger.warning("ignore corrupted email. Unknown token %d" % comment_id) return # retrieve site and comment rows - comment = Comment.get_by_id(comment_id) + comment = Comment if not comment: - logger.warn("unknown comment %d" % comment_id) + logger.warning("unknown comment %d" % comment_id) return True if comment.published: - logger.warn("ignore already published email. token %d" % comment_id) + logger.warning("ignore already published email. token %d" % comment_id) return if not email.plain_text_content: - logger.warn("ignore empty email") + logger.warning("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if email.plain_text_content[:2].upper() in ("NO"): + if email.plain_text_content[:2].upper() == "NO": logger.info("discard comment: %d" % comment_id) comment.delete_instance() new_email_body = templater.get_template(lang, Template.DROP_COMMENT).render( original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): - logger.warn("minor failure. cannot send rejection mail " + email.subject) + logger.warning("minor failure. cannot send rejection mail " + email.subject) else: # save publishing datetime - comment.publish() + dao.publish(comment) logger.info("commit comment: %d" % comment_id) # rebuild RSS @@ -73,7 +74,7 @@ def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_toke original=email.plain_text_content ) if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): - logger.warn("minor failure. cannot send approval email " + email.subject) + logger.warning("minor failure. cannot send approval email " + email.subject) return True @@ -100,6 +101,6 @@ def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): logger.debug("new comment processed ") # notify site admin and save notification datetime - comment.notify_site_admin() + dao.notify_site_admin(comment) else: - logger.warn("rescheduled. send mail failure " + subject) + logger.warning("rescheduled. send mail failure " + subject) diff --git a/stacosys/db/__init__.py b/stacosys/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py new file mode 100644 index 0000000..ddf95cc --- /dev/null +++ b/stacosys/db/dao.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +from datetime import datetime + +from stacosys.model.comment import Comment + + +def notify_site_admin(comment: Comment): + comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + + +def publish(comment: Comment): + comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.save() + + + diff --git a/stacosys/core/database.py b/stacosys/db/database.py similarity index 100% rename from stacosys/core/database.py rename to stacosys/db/database.py diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index e586b25..48b3e49 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -5,7 +5,7 @@ from peewee import CharField from peewee import TextField from peewee import DateTimeField from datetime import datetime -from stacosys.core.database import BaseModel +from stacosys.db.database import BaseModel class Comment(BaseModel): @@ -17,11 +17,3 @@ class Comment(BaseModel): author_site = CharField(default="") author_gravatar = CharField(default="") content = TextField() - - def notify_site_admin(self): - self.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.save() - - def publish(self): - self.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.save() From 3eb1b86246410fc6ee067fdb1def0b266131a949 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 11:42:04 +0200 Subject: [PATCH 351/586] replace pytest by unittest --- tests/test_config.py | 83 ++++++++++++++++++++--------------------- tests/test_form.py | 22 +++++++++++ tests/test_stacosys.py | 8 +++- tests/test_templater.py | 80 +++++++++++++++++++-------------------- 4 files changed, 107 insertions(+), 86 deletions(-) create mode 100644 tests/test_form.py diff --git a/tests/test_config.py b/tests/test_config.py index 3df0502..6b4eac1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,8 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import pytest +import unittest + from stacosys.conf.config import Config, ConfigParameter EXPECTED_DB_SQLITE_FILE = "db.sqlite" @@ -10,48 +11,46 @@ EXPECTED_IMAP_PORT = "5000" EXPECTED_IMAP_LOGIN = "user" -@pytest.fixture -def conf(): - conf = Config() - conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) - conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(ConfigParameter.SMTP_STARTTLS, "yes") - conf.put(ConfigParameter.IMAP_SSL, "false") - return conf +class ConfigTestCase(unittest.TestCase): + def conf(self): + conf = Config() + conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) + conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) + conf.put(ConfigParameter.SMTP_STARTTLS, "yes") + conf.put(ConfigParameter.IMAP_SSL, "false") + return conf -def test_exists(conf): - assert conf is not None - assert conf.exists(ConfigParameter.DB_SQLITE_FILE) - assert not conf.exists(ConfigParameter.IMAP_HOST) + def test_exists(self): + conf = self.conf() + self.assertTrue(conf.exists(ConfigParameter.DB_SQLITE_FILE)) + self.assertFalse(conf.exists(ConfigParameter.IMAP_HOST)) + def test_get(self): + conf = self.conf() + self.assertEqual(conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE) + self.assertEqual(conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) + self.assertIsNone(conf.get(ConfigParameter.HTTP_HOST)) + self.assertEqual(conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) + self.assertEqual(conf.get(ConfigParameter.IMAP_PORT), EXPECTED_IMAP_PORT) + self.assertEqual(conf.get_int(ConfigParameter.IMAP_PORT), int(EXPECTED_IMAP_PORT)) + try: + conf.get_int(ConfigParameter.HTTP_PORT) + self.assertTrue(False) + except Exception: + pass + self.assertTrue(conf.get_bool(ConfigParameter.SMTP_STARTTLS)) + self.assertFalse(conf.get_bool(ConfigParameter.IMAP_SSL)) + try: + conf.get_bool(ConfigParameter.DB_URL) + self.assertTrue(False) + except Exception: + pass -def test_get(conf): - assert conf is not None - assert conf.get(ConfigParameter.DB_SQLITE_FILE) == EXPECTED_DB_SQLITE_FILE - assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(ConfigParameter.HTTP_HOST) is None - assert conf.get(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT - assert conf.get(ConfigParameter.IMAP_PORT) == EXPECTED_IMAP_PORT - assert conf.get_int(ConfigParameter.IMAP_PORT) == int(EXPECTED_IMAP_PORT) - try: - conf.get_int(ConfigParameter.HTTP_PORT) - assert False - except Exception: - pass - assert conf.get_bool(ConfigParameter.SMTP_STARTTLS) - assert not conf.get_bool(ConfigParameter.IMAP_SSL) - try: - conf.get_bool(ConfigParameter.DB_URL) - assert False - except Exception: - pass - - -def test_put(conf): - assert conf is not None - assert not conf.exists(ConfigParameter.IMAP_LOGIN) - conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - assert conf.exists(ConfigParameter.IMAP_LOGIN) - assert conf.get(ConfigParameter.IMAP_LOGIN) == EXPECTED_IMAP_LOGIN + def test_put(self): + conf = self.conf() + self.assertFalse(conf.exists(ConfigParameter.IMAP_LOGIN)) + conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + self.assertTrue(conf.exists(ConfigParameter.IMAP_LOGIN)) + self.assertEqual(conf.get(ConfigParameter.IMAP_LOGIN), EXPECTED_IMAP_LOGIN) diff --git a/tests/test_form.py b/tests/test_form.py new file mode 100644 index 0000000..f388c6a --- /dev/null +++ b/tests/test_form.py @@ -0,0 +1,22 @@ +import unittest + +from stacosys.interface import form + + +class FormInterfaceTestCase(unittest.TestCase): + + def test_check_form_data_ok(self): + d = {"url": "/", "message": "", "site": "", "remarque": "", "author": "", "token": "", "email": ""} + self.assertTrue(form.check_form_data(d)) + d = {"url": "/"} + self.assertTrue(form.check_form_data(d)) + d = {} + self.assertTrue(form.check_form_data(d)) + + def test_check_form_data_ko(self): + d = {"url": "/", "message": "", "site": "", "remarque": "", "author": "", "token": "", "email": "", "bonus": ""} + self.assertFalse(form.check_form_data(d)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 94714f0..23b2aaa 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -1,5 +1,9 @@ +import unittest + from stacosys import __version__ -def test_version(): - assert __version__ == "2.0" +class StacosysTestCase(unittest.TestCase): + + def test_version(self): + self.assertEqual("2.0", __version__) diff --git a/tests/test_templater.py b/tests/test_templater.py index aad652b..117d19d 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -2,55 +2,51 @@ # -*- coding: UTF-8 -*- import os +import unittest from stacosys.core.templater import Templater, Template -def get_template_content(lang, template_name, **kwargs): - current_path = os.path.dirname(__file__) - template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - templater = Templater(template_path) - template = templater.get_template(lang, template_name) - assert template - return template.render(kwargs) +class TemplateTestCase(unittest.TestCase): + def get_template_content(self, lang, template_name, **kwargs): + current_path = os.path.dirname(__file__) + template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) + template = Templater(template_path).get_template(lang, template_name) + return template.render(kwargs) -def test_approve_comment(): - content = get_template_content("fr", Template.APPROVE_COMMENT, original="[texte]") - assert content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.") - assert content.endswith("[texte]") - content = get_template_content("en", Template.APPROVE_COMMENT, original="[texte]") - assert content.startswith("Hi,\n\nThe comment should be published soon.") - assert content.endswith("[texte]") + def test_approve_comment(self): + content = self.get_template_content("fr", Template.APPROVE_COMMENT, original="[texte]") + self.assertTrue(content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.")) + self.assertTrue(content.endswith("[texte]")) + content = self.get_template_content("en", Template.APPROVE_COMMENT, original="[texte]") + self.assertTrue(content.startswith("Hi,\n\nThe comment should be published soon.")) + self.assertTrue(content.endswith("[texte]")) + def test_drop_comment(self): + content = self.get_template_content("fr", Template.DROP_COMMENT, original="[texte]") + self.assertTrue(content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.")) + self.assertTrue(content.endswith("[texte]")) + content = self.get_template_content("en", Template.DROP_COMMENT, original="[texte]") + self.assertTrue(content.startswith("Hi,\n\nThe comment will not be published.")) + self.assertTrue(content.endswith("[texte]")) -def test_drop_comment(): - content = get_template_content("fr", Template.DROP_COMMENT, original="[texte]") - assert content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.") - assert content.endswith("[texte]") - content = get_template_content("en", Template.DROP_COMMENT, original="[texte]") - assert content.startswith("Hi,\n\nThe comment will not be published.") - assert content.endswith("[texte]") + def test_new_comment(self): + content = self.get_template_content("fr", Template.NEW_COMMENT, comment="[comment]") + self.assertTrue(content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté")) + self.assertTrue(content.endswith("[comment]\n\n--\nStacosys")) + content = self.get_template_content("en", Template.NEW_COMMENT, comment="[comment]") + self.assertTrue(content.startswith("Hi,\n\nA new comment has been submitted")) + self.assertTrue(content.endswith("[comment]\n\n--\nStacosys")) + def test_notify_message(self): + content = self.get_template_content("fr", Template.NOTIFY_MESSAGE) + self.assertEqual("Nouveau commentaire", content) + content = self.get_template_content("en", Template.NOTIFY_MESSAGE) + self.assertEqual("New comment", content) -def test_new_comment(): - content = get_template_content("fr", Template.NEW_COMMENT, comment="[comment]") - assert content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté") - assert content.endswith("[comment]\n\n--\nStacosys") - content = get_template_content("en", Template.NEW_COMMENT, comment="[comment]") - assert content.startswith("Hi,\n\nA new comment has been submitted") - assert content.endswith("[comment]\n\n--\nStacosys") - - -def test_notify_message(): - content = get_template_content("fr", Template.NOTIFY_MESSAGE) - assert content == "Nouveau commentaire" - content = get_template_content("en", Template.NOTIFY_MESSAGE) - assert content == "New comment" - - -def test_rss_title(): - content = get_template_content("fr", Template.RSS_TITLE_MESSAGE, site="[site]") - assert content == "[site] : commentaires" - content = get_template_content("en", Template.RSS_TITLE_MESSAGE, site="[site]") - assert content == "[site] : comments" + def test_rss_title(self): + content = self.get_template_content("fr", Template.RSS_TITLE_MESSAGE, site="[site]") + self.assertEqual("[site] : commentaires", content) + content = self.get_template_content("en", Template.RSS_TITLE_MESSAGE, site="[site]") + self.assertEqual("[site] : comments", content) From c175b4a120b868df8ed47ed8d10ce84388c0afe1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 11:42:40 +0200 Subject: [PATCH 352/586] move DB operations to DAO --- poetry.lock | 135 +------------------------------------ pyproject.toml | 2 - stacosys/core/cron.py | 68 +++++++++---------- stacosys/db/dao.py | 39 ++++++++++- stacosys/db/database.py | 24 ------- stacosys/interface/api.py | 23 ++----- stacosys/interface/form.py | 37 ++++------ stacosys/model/comment.py | 4 +- 8 files changed, 94 insertions(+), 238 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0d42293..1b1827a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,28 +32,6 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "21.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - [[package]] name = "black" version = "20.8b1" @@ -179,14 +157,6 @@ category = "main" optional = false python-versions = ">=3.5" -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "itsdangerous" version = "2.0.1" @@ -260,17 +230,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "packaging" -version = "21.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2" - [[package]] name = "pathspec" version = "0.8.1" @@ -287,17 +246,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "pluggy" -version = "0.13.1" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -dev = ["pre-commit", "tox"] - [[package]] name = "profig" version = "0.5.1" @@ -306,14 +254,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "pycodestyle" version = "2.7.0" @@ -330,14 +270,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "pyrss2gen" version = "1.1" @@ -346,27 +278,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "pytest" -version = "6.2.4" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<1.0.0a1" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - [[package]] name = "python-dateutil" version = "2.8.2" @@ -431,14 +342,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "tinydb" -version = "4.5.0" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" -category = "main" -optional = false -python-versions = ">=3.5,<4.0" - [[package]] name = "toml" version = "0.10.2" @@ -501,7 +404,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "c2c76edb4fcae80581572bcb9699f0b8b0193cbcc5e08a4b6f431c15f09236e7" +content-hash = "8190054ee0a6bf5fccefd841ac71fa6851a4e7d057c5af6fb83ea2364711cb78" [metadata.files] appdirs = [ @@ -512,14 +415,6 @@ apscheduler = [ {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -558,10 +453,6 @@ idna = [ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] itsdangerous = [ {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, @@ -634,10 +525,6 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, -] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, @@ -645,17 +532,9 @@ pathspec = [ peewee = [ {file = "peewee-3.14.4.tar.gz", hash = "sha256:9e356b327c2eaec6dd42ecea6f4ddded025793dba906a3d065a0452e726c51a2"}, ] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] profig = [ {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, ] -py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, -] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, @@ -664,17 +543,9 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] -pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, -] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -739,10 +610,6 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -tinydb = [ - {file = "tinydb-4.5.0-py3-none-any.whl", hash = "sha256:ab2669b88ba1e1b3e1bd6da1a1e3ee284fde6fbf327fb16a206ac3954915f37f"}, - {file = "tinydb-4.5.0.tar.gz", hash = "sha256:d287cd092f19a2b8553d0a6018f172c351268fb8619898eb87633d9e2c036344"}, -] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, diff --git a/pyproject.toml b/pyproject.toml index e3bd86c..b7207eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ pyrss2gen = "^1.1" profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" -tinydb = "^4.3.0" Flask = "^2.0.1" peewee = "^3.14.0" requests = "^2.25.1" @@ -23,7 +22,6 @@ rope = "^0.16.0" mypy = "^0.790" flake8-black = "^0.2.1" black = "^20.8b1" -pytest = "^6.2.1" [build-system] requires = ["poetry>=0.12"] diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 0cd59e1..b4525e3 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -5,12 +5,13 @@ import logging import os import re -from stacosys.core.templater import Templater, Template -from stacosys.model.comment import Comment -from stacosys.model.email import Email -from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer +from stacosys.core.rss import Rss +from stacosys.core.templater import Templater, Template from stacosys.db import dao +from stacosys.model.email import Email + +REGEX_EMAIL_SUBJECT = r".*STACOSYS.*\[(\d+)\:(\w+)\]" logger = logging.getLogger(__name__) @@ -21,41 +22,42 @@ templater = Templater(template_path) def fetch_mail_answers(lang, mailer: Mailer, rss: Rss, site_token): for msg in mailer.fetch(): - if re.search(r".*STACOSYS.*\[(\d+)\:(\w+)\]", msg.subject, re.DOTALL): - if _reply_comment_email(lang, mailer, rss, msg, site_token): - mailer.delete(msg.id) + # filter stacosys e-mails + m = re.search(REGEX_EMAIL_SUBJECT, msg.subject, re.DOTALL) + if not m: + continue + + comment_id = int(m.group(1)) + submitted_token = m.group(2) + + # validate token + if submitted_token != site_token: + logger.warning("ignore corrupted email. Unknown token %d" % comment_id) + continue + + if not msg.plain_text_content: + logger.warning("ignore empty email") + continue + + _reply_comment_email(lang, mailer, rss, msg, comment_id) + mailer.delete(msg.id) -def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_token): - - m = re.search(r"\[(\d+)\:(\w+)\]", email.subject) - if not m: - logger.warning("ignore corrupted email. No token %s" % email.subject) - return - comment_id = int(m.group(1)) - token = m.group(2) - if token != site_token: - logger.warning("ignore corrupted email. Unknown token %d" % comment_id) - return - - # retrieve site and comment rows - comment = Comment +def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, comment_id): + # retrieve comment + comment = dao.find_comment_by_id(comment_id) if not comment: logger.warning("unknown comment %d" % comment_id) - return True + return if comment.published: logger.warning("ignore already published email. token %d" % comment_id) return - if not email.plain_text_content: - logger.warning("ignore empty email") - return - # safe logic: no answer or unknown answer is a go for publishing if email.plain_text_content[:2].upper() == "NO": logger.info("discard comment: %d" % comment_id) - comment.delete_instance() + dao.delete_comment(comment) new_email_body = templater.get_template(lang, Template.DROP_COMMENT).render( original=email.plain_text_content ) @@ -63,7 +65,7 @@ def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_toke logger.warning("minor failure. cannot send rejection mail " + email.subject) else: # save publishing datetime - dao.publish(comment) + dao.publish_comment(comment) logger.info("commit comment: %d" % comment_id) # rebuild RSS @@ -76,11 +78,9 @@ def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, site_toke if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): logger.warning("minor failure. cannot send approval email " + email.subject) - return True - def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): - for comment in Comment.select().where(Comment.notified.is_null()): + for comment in dao.find_not_notified_comments(): comment_list = ( "author: %s" % comment.author_name, "site: %s" % comment.author_site, @@ -95,12 +95,12 @@ def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): url=comment.url, comment=comment_text ) - # send email + # send email to notify admin subject = "STACOSYS %s: [%d:%s]" % (site_name, comment.id, site_token) if mailer.send(site_admin_email, subject, email_body): logger.debug("new comment processed ") - # notify site admin and save notification datetime - dao.notify_site_admin(comment) + # save notification datetime + dao.notify_comment(comment) else: logger.warning("rescheduled. send mail failure " + subject) diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index ddf95cc..05a2777 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -5,14 +5,49 @@ from datetime import datetime from stacosys.model.comment import Comment -def notify_site_admin(comment: Comment): +def find_comment_by_id(id): + return Comment.get_by_id(id) + + +def notify_comment(comment: Comment): comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() -def publish(comment: Comment): +def publish_comment(comment: Comment): comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() +def delete_comment(comment: Comment): + comment.delete_instance() + +def find_not_notified_comments(): + return Comment.select().where(Comment.notified.is_null()) + + +def find_published_comments_by_url(url): + return Comment.select(Comment).where((Comment.url == url) & (Comment.published.is_null(False))).order_by( + +Comment.published) + + +def count_published_comments(url): + return Comment.select(Comment).where( + (Comment.url == url) & (Comment.published.is_null(False))).count() if url else Comment.select(Comment).where( + Comment.publishd.is_null(False)).count() + + +def create_comment(url, author_name, author_site, author_gravatar, message): + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment = Comment( + url=url, + author_name=author_name, + author_site=author_site, + author_gravatar=author_gravatar, + content=message, + created=created, + notified=None, + published=None, + ) + comment.save() diff --git a/stacosys/db/database.py b/stacosys/db/database.py index 45b89fb..5b3af96 100644 --- a/stacosys/db/database.py +++ b/stacosys/db/database.py @@ -1,11 +1,8 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import json from peewee import Model from playhouse.db_url import SqliteDatabase -from playhouse.shortcuts import model_to_dict -from tinydb import TinyDB db = SqliteDatabase(None) @@ -20,29 +17,8 @@ class Database: return db def setup(self, db_url): - db.init(db_url) db.connect() from stacosys.model.comment import Comment db.create_tables([Comment], safe=True) - - -# if config.exists(config.DB_BACKUP_JSON_FILE): -# _backup_db(config.DB_BACKUP_JSON_FILE, Comment) - - -def _tojson_model(comment): - dcomment = model_to_dict(comment) - # del dcomment["site"] - tcomment = json.dumps(dcomment, indent=4, sort_keys=True, default=str) - return json.loads(tcomment) - - -def _backup_db(db_file, Comment): - db = TinyDB(db_file, sort_keys=True, indent=4, separators=(",", ": ")) - db.drop_tables() - table = db.table("comments") - for comment in Comment.select(): - cc = _tojson_model(comment) - table.insert(cc) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index a3a8dea..22735c5 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -2,14 +2,15 @@ # -*- coding: utf-8 -*- import logging + from flask import abort, jsonify, request + +from stacosys.db import dao from stacosys.interface import app -from stacosys.model.comment import Comment logger = logging.getLogger(__name__) - @app.route("/ping", methods=["GET"]) def ping(): return "OK" @@ -23,12 +24,8 @@ def query_comments(): abort(401) url = request.args.get("url", "") - logger.info("retrieve comments for url %s" % (url)) - for comment in ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .order_by(+Comment.published) - ): + logger.info("retrieve comments for url %s" % url) + for comment in dao.find_published_comments_by_url(url): d = { "author": comment.author_name, "content": comment.content, @@ -48,12 +45,4 @@ def get_comments_count(): if token != app.config.get("SITE_TOKEN"): abort(401) url = request.args.get("url", "") - if url: - count = ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .count() - ) - else: - count = Comment.select(Comment).where(Comment.publishd.is_null(False)).count() - return jsonify({"count": count}) + return jsonify({"count": dao.count_published_comments(url)}) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 192b6a2..8c288c0 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -2,18 +2,17 @@ # -*- coding: utf-8 -*- import logging -from datetime import datetime + from flask import abort, redirect, request +from stacosys.db import dao from stacosys.interface import app -from stacosys.model.comment import Comment logger = logging.getLogger(__name__) @app.route("/newcomment", methods=["POST"]) def new_form_comment(): - try: data = request.form logger.info("form data " + str(data)) @@ -26,7 +25,7 @@ def new_form_comment(): # honeypot for spammers captcha = data.get("remarque", "") if captcha: - logger.warn("discard spam: data %s" % data) + logger.warning("discard spam: data %s" % data) abort(400) url = data.get("url", "") @@ -39,23 +38,14 @@ def new_form_comment(): # anti-spam again if not url or not author_name or not message: - logger.warn("empty field: data %s" % data) + logger.warning("empty field: data %s" % data) + abort(400) + if not check_form_data(data.to_dict()): + logger.warning("additional field: data %s" % data) abort(400) - check_form_data(data) # add a row to Comment table - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment = Comment( - url=url, - author_name=author_name, - author_site=author_site, - author_gravatar=author_gravatar, - content=message, - created=created, - notified=None, - published=None, - ) - comment.save() + dao.create_comment(url, author_name, author_site, author_gravatar, message) except Exception: logger.exception("new comment failure") @@ -64,12 +54,13 @@ def new_form_comment(): return redirect("/redirect/", code=302) -def check_form_data(data): +def check_form_data(d): fields = ["url", "message", "site", "remarque", "author", "token", "email"] - d = data.to_dict() for field in fields: if field in d: del d[field] - if d: - logger.warn("additional field: data %s" % data) - abort(400) + +# filtered = dict(filter(lambda x: x[0] not in fields, data.to_dict().items())) + return not d + + diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 48b3e49..843e8b6 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -2,9 +2,9 @@ # -*- coding: UTF-8 -*- from peewee import CharField -from peewee import TextField from peewee import DateTimeField -from datetime import datetime +from peewee import TextField + from stacosys.db.database import BaseModel From b9039a4eb7d1377f1f4fd13c910efe72e559a67b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 18:32:29 +0200 Subject: [PATCH 353/586] Improve unit test --- tests/test_config.py | 56 +++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 6b4eac1..3bee34b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,44 +13,36 @@ EXPECTED_IMAP_LOGIN = "user" class ConfigTestCase(unittest.TestCase): - def conf(self): - conf = Config() - conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) - conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) - conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) - conf.put(ConfigParameter.SMTP_STARTTLS, "yes") - conf.put(ConfigParameter.IMAP_SSL, "false") - return conf + def setUp(self): + self.conf = Config() + self.conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) + self.conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + self.conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) + self.conf.put(ConfigParameter.SMTP_STARTTLS, "yes") + self.conf.put(ConfigParameter.IMAP_SSL, "false") def test_exists(self): - conf = self.conf() - self.assertTrue(conf.exists(ConfigParameter.DB_SQLITE_FILE)) - self.assertFalse(conf.exists(ConfigParameter.IMAP_HOST)) + self.assertTrue(self.conf.exists(ConfigParameter.DB_SQLITE_FILE)) + self.assertFalse(self.conf.exists(ConfigParameter.IMAP_HOST)) def test_get(self): - conf = self.conf() - self.assertEqual(conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE) - self.assertEqual(conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) - self.assertIsNone(conf.get(ConfigParameter.HTTP_HOST)) - self.assertEqual(conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) - self.assertEqual(conf.get(ConfigParameter.IMAP_PORT), EXPECTED_IMAP_PORT) - self.assertEqual(conf.get_int(ConfigParameter.IMAP_PORT), int(EXPECTED_IMAP_PORT)) + self.assertEqual(self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE) + self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) + self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) + self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) + self.assertEqual(self.conf.get(ConfigParameter.IMAP_PORT), EXPECTED_IMAP_PORT) + self.assertEqual(self.conf.get_int(ConfigParameter.IMAP_PORT), int(EXPECTED_IMAP_PORT)) + self.assertEqual(self.conf.get_int(ConfigParameter.HTTP_PORT), 8080) + self.assertTrue(self.conf.get_bool(ConfigParameter.SMTP_STARTTLS)) + self.assertFalse(self.conf.get_bool(ConfigParameter.IMAP_SSL)) try: - conf.get_int(ConfigParameter.HTTP_PORT) + self.conf.get_bool(ConfigParameter.DB_SQLITE_FILE) self.assertTrue(False) - except Exception: - pass - self.assertTrue(conf.get_bool(ConfigParameter.SMTP_STARTTLS)) - self.assertFalse(conf.get_bool(ConfigParameter.IMAP_SSL)) - try: - conf.get_bool(ConfigParameter.DB_URL) - self.assertTrue(False) - except Exception: + except AssertionError: pass def test_put(self): - conf = self.conf() - self.assertFalse(conf.exists(ConfigParameter.IMAP_LOGIN)) - conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - self.assertTrue(conf.exists(ConfigParameter.IMAP_LOGIN)) - self.assertEqual(conf.get(ConfigParameter.IMAP_LOGIN), EXPECTED_IMAP_LOGIN) + self.assertFalse(self.conf.exists(ConfigParameter.IMAP_LOGIN)) + self.conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) + self.assertTrue(self.conf.exists(ConfigParameter.IMAP_LOGIN)) + self.assertEqual(self.conf.get(ConfigParameter.IMAP_LOGIN), EXPECTED_IMAP_LOGIN) From 743c88f98bd28fa7a9f97da43802f7077517e08c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 17 Jul 2021 18:32:47 +0200 Subject: [PATCH 354/586] Enforce boolean validation --- stacosys/conf/config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 8b8c0a2..e942bb2 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- from enum import Enum + import profig class ConfigParameter(Enum): DB_SQLITE_FILE = "main.db_sqlite_file" - DB_BACKUP_JSON_FILE = "main.db_backup_json_file" LANG = "main.lang" COMMENT_POLLING = "main.newcomment_polling" @@ -62,7 +62,9 @@ class Config: return int(self._params[key.value]) def get_bool(self, key: ConfigParameter): - return self._params[key.value].lower() in ("yes", "true") + value = self._params[key.value].lower() + assert value in ("yes", "true", "no", "false") + return value in ("yes", "true") def __repr__(self): - return self._params.__repr__() \ No newline at end of file + return self._params.__repr__() From bcc7a8d6b7fa54d00206b6ba70485c91c77e1719 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:17:10 +0200 Subject: [PATCH 355/586] unit test dao --- stacosys/db/dao.py | 3 ++- tests/test_db.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/test_db.py diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 05a2777..36ebdd4 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -35,7 +35,7 @@ def find_published_comments_by_url(url): def count_published_comments(url): return Comment.select(Comment).where( (Comment.url == url) & (Comment.published.is_null(False))).count() if url else Comment.select(Comment).where( - Comment.publishd.is_null(False)).count() + Comment.published.is_null(False)).count() def create_comment(url, author_name, author_site, author_gravatar, message): @@ -51,3 +51,4 @@ def create_comment(url, author_name, author_site, author_gravatar, message): published=None, ) comment.save() + return comment diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..2d99936 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,55 @@ +import unittest + +from stacosys.db import dao +from stacosys.db import database + + +class DbTestCase(unittest.TestCase): + + def setUp(self): + db = database.Database() + db.setup(":memory:") + + def test_dao_published(self): + + # test count published + self.assertEqual(0, dao.count_published_comments("")) + c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") + self.assertEqual(0, dao.count_published_comments("")) + dao.publish_comment(c1) + self.assertEqual(1, dao.count_published_comments("")) + c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") + dao.publish_comment(c2) + self.assertEqual(2, dao.count_published_comments("")) + c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") + dao.publish_comment(c3) + self.assertEqual(1, dao.count_published_comments("/post1")) + self.assertEqual(2, dao.count_published_comments("/post2")) + + # test find published + self.assertEqual(0, len(dao.find_published_comments_by_url("/"))) + self.assertEqual(1, len(dao.find_published_comments_by_url("/post1"))) + self.assertEqual(2, len(dao.find_published_comments_by_url("/post2"))) + + dao.delete_comment(c1) + self.assertEqual(0, len(dao.find_published_comments_by_url("/post1"))) + + def test_dao_notified(self): + + # test count notified + self.assertEqual(0, len(dao.find_not_notified_comments())) + c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") + self.assertEqual(1, len(dao.find_not_notified_comments())) + c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") + self.assertEqual(2, len(dao.find_not_notified_comments())) + dao.notify_comment(c1) + dao.notify_comment(c2) + self.assertEqual(0, len(dao.find_not_notified_comments())) + c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") + self.assertEqual(1, len(dao.find_not_notified_comments())) + dao.notify_comment(c3) + self.assertEqual(0, len(dao.find_not_notified_comments())) + + +if __name__ == '__main__': + unittest.main() From 441f2cb6c8f8518f8ef60f0d21268684db5c0435 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:17:26 +0200 Subject: [PATCH 356/586] public access rest api --- stacosys/interface/api.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 22735c5..5997250 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -19,9 +19,6 @@ def ping(): @app.route("/comments", methods=["GET"]) def query_comments(): comments = [] - token = request.args.get("token", "") - if token != app.config.get("SITE_TOKEN"): - abort(401) url = request.args.get("url", "") logger.info("retrieve comments for url %s" % url) From 882d689609640500fb78779c9bd11dc3a12e8bfd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:17:46 +0200 Subject: [PATCH 357/586] generate token by program --- config.ini | 2 -- run.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.ini b/config.ini index ee0abe2..224b7e6 100755 --- a/config.ini +++ b/config.ini @@ -3,13 +3,11 @@ [main] lang = fr db_sqlite_file = db.sqlite -db_backup_json_file = db.json newcomment_polling = 60 [site] name = "My blog" url = http://blog.mydomain.com -token = admin_email = admin@mydomain.com [http] diff --git a/run.py b/run.py index 3496f48..854ea82 100644 --- a/run.py +++ b/run.py @@ -5,6 +5,7 @@ import sys import os import argparse import logging +import hashlib from stacosys.conf.config import Config, ConfigParameter from stacosys.db import database @@ -91,6 +92,7 @@ def stacosys_server(config_pathname): app.logger.addHandler(mail_handler) # configure scheduler + conf.set(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME)).hexdigest()) scheduler.configure( conf.get_int(ConfigParameter.IMAP_POLLING), conf.get_int(ConfigParameter.COMMENT_POLLING), From 9c3d08858434a5d848ae02ff71afe5ab715dc546 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:21:10 +0200 Subject: [PATCH 358/586] public access rest api --- stacosys/interface/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 5997250..cab2905 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -3,7 +3,7 @@ import logging -from flask import abort, jsonify, request +from flask import jsonify, request from stacosys.db import dao from stacosys.interface import app @@ -38,8 +38,5 @@ def query_comments(): @app.route("/comments/count", methods=["GET"]) def get_comments_count(): - token = request.args.get("token", "") - if token != app.config.get("SITE_TOKEN"): - abort(401) url = request.args.get("url", "") return jsonify({"count": dao.count_published_comments(url)}) From 1c403ae8b3981ce8451bee7ff0e19cabe44ad1a7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 05:28:32 +0200 Subject: [PATCH 359/586] improve form --- stacosys/interface/form.py | 67 +++++++++++++++----------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 8c288c0..eb5b826 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -13,54 +13,41 @@ logger = logging.getLogger(__name__) @app.route("/newcomment", methods=["POST"]) def new_form_comment(): - try: - data = request.form - logger.info("form data " + str(data)) - # validate token: retrieve site entity - token = data.get("token", "") - if token != app.config.get("SITE_TOKEN"): - abort(401) + data = request.form + logger.info("form data " + str(data)) - # honeypot for spammers - captcha = data.get("remarque", "") - if captcha: - logger.warning("discard spam: data %s" % data) - abort(400) - - url = data.get("url", "") - author_name = data.get("author", "").strip() - author_gravatar = data.get("email", "").strip() - author_site = data.get("site", "").lower().strip() - if author_site and author_site[:4] != "http": - author_site = "http://" + author_site - message = data.get("message", "") - - # anti-spam again - if not url or not author_name or not message: - logger.warning("empty field: data %s" % data) - abort(400) - if not check_form_data(data.to_dict()): - logger.warning("additional field: data %s" % data) - abort(400) - - # add a row to Comment table - dao.create_comment(url, author_name, author_site, author_gravatar, message) - - except Exception: - logger.exception("new comment failure") + # honeypot for spammers + captcha = data.get("remarque", "") + if captcha: + logger.warning("discard spam: data %s" % data) abort(400) + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") + + # anti-spam again + if not url or not author_name or not message: + logger.warning("empty field: data %s" % data) + abort(400) + if not check_form_data(data.to_dict()): + logger.warning("additional field: data %s" % data) + abort(400) + + # add a row to Comment table + dao.create_comment(url, author_name, author_site, author_gravatar, message) + return redirect("/redirect/", code=302) def check_form_data(d): fields = ["url", "message", "site", "remarque", "author", "token", "email"] - for field in fields: - if field in d: - del d[field] - -# filtered = dict(filter(lambda x: x[0] not in fields, data.to_dict().items())) - return not d + filtered = dict(filter(lambda x: x[0] not in fields, d.items())) + return not filtered From 6cbbbbd5e5c229125e91049c559fa562201cef71 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 05:42:39 +0200 Subject: [PATCH 360/586] Docker Image CI --- .github/workflows/main.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..7f0842d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,14 @@ +name: Docker Image CI +on: + push: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: | + echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io + docker build . --file Dockerfile --tag docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA + docker push docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA From e5491528c45d9984b5e19bd7befe451b0836c550 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 05:43:06 +0200 Subject: [PATCH 361/586] docker --- Dockerfile | 18 ++++++++++++++++++ docker/docker-init.sh | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 Dockerfile create mode 100644 docker/docker-init.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a09258 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.9-alpine + +ARG STACOSYS_VERSION=2.0 +ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl + +RUN apk update && apk add bash && apk add wget && rm -rf /var/cache/apk/* + +COPY docker/docker-init.sh /usr/local/bin/ +RUN chmod +x usr/local/bin/docker-init.sh + +RUN cd / +#COPY ${STACOSYS_FILENAME} / +RUN wget https://github.com/kianby/stacosys/releases/download/${STACOSYS_VERSION}/${STACOSYS_FILENAME} +RUN python3 -m pip install ${STACOSYS_FILENAME} --target /stacosys +RUN rm -f ${STACOSYS_FILENAME} + +WORKDIR /stacosys +CMD ["docker-init.sh"] diff --git a/docker/docker-init.sh b/docker/docker-init.sh new file mode 100644 index 0000000..b573f35 --- /dev/null +++ b/docker/docker-init.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd /stacosys +python3 run.py /config/config.ini From 5cc8765c49ac9bb7469812da065efd623e345d04 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 05:47:40 +0200 Subject: [PATCH 362/586] Bump version 2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7207eb..fc9df59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0b4" +version = "2.0" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From 9fad002faf792a3d7e0289da9703fa3d2537f943 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 14:22:24 +0200 Subject: [PATCH 363/586] fix unicode encoding --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index 854ea82..0d4b92c 100644 --- a/run.py +++ b/run.py @@ -92,7 +92,7 @@ def stacosys_server(config_pathname): app.logger.addHandler(mail_handler) # configure scheduler - conf.set(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME)).hexdigest()) + conf.put(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME).encode('utf-8')).hexdigest()) scheduler.configure( conf.get_int(ConfigParameter.IMAP_POLLING), conf.get_int(ConfigParameter.COMMENT_POLLING), From 12d3c92abb4da75f090d48be7f4420a5546b2440 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 14:27:11 +0200 Subject: [PATCH 364/586] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index feaed5e..15964de 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Stacosys can be hosted on the same server or on a different server than the blog *How do you block spammers?* -- Current comment form is basic: no captcha support but a honey pot. +- Current comment form is basic: no captcha support but protected by an honey pot. *Which database is used?* From db056f40dd2ee328f622ce5320e6c5701c4d23e6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 31 Jul 2021 14:57:53 +0200 Subject: [PATCH 365/586] build and push latest --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f0842d..6069539 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,5 +10,5 @@ jobs: - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io - docker build . --file Dockerfile --tag docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA - docker push docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA + docker build . --file Dockerfile --tag docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest + docker push docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest From 9022e469d72bacca79c4b8a1d1a3e2fa4df257dd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:42:50 +0200 Subject: [PATCH 366/586] Rename main.yml to docker.yml --- .github/workflows/{main.yml => docker.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{main.yml => docker.yml} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/docker.yml similarity index 100% rename from .github/workflows/main.yml rename to .github/workflows/docker.yml From 0a7af0031e922836e66f5bf208f24839b6b6e523 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:43:50 +0200 Subject: [PATCH 367/586] Create pytest --- .github/workflows/pytest | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pytest diff --git a/.github/workflows/pytest b/.github/workflows/pytest new file mode 100644 index 0000000..085426c --- /dev/null +++ b/.github/workflows/pytest @@ -0,0 +1,23 @@ +name: pytest +on: push + +jobs: + ci: + strategy: + fail-fast: false + matrix: + python-version: [3.6, 3.7, 3.8] + poetry-version: [1.0, 1.1.6] + os: [ubuntu-18.04, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: poetry pytest + run: poetry run pytest From 26a11461222d8c8612681012b766276e142c22c9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:44:18 +0200 Subject: [PATCH 368/586] add pytest to poetry --- poetry.lock | 123 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 1b1827a..4d42e97 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,6 +32,28 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + [[package]] name = "black" version = "20.8b1" @@ -157,6 +179,14 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "itsdangerous" version = "2.0.1" @@ -230,6 +260,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + [[package]] name = "pathspec" version = "0.8.1" @@ -246,6 +287,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + [[package]] name = "profig" version = "0.5.1" @@ -254,6 +306,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pycodestyle" version = "2.7.0" @@ -270,6 +330,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "pyrss2gen" version = "1.1" @@ -278,6 +346,27 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -404,7 +493,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "8190054ee0a6bf5fccefd841ac71fa6851a4e7d057c5af6fb83ea2364711cb78" +content-hash = "e2969fb3de45c3e290504db65fd32ca958b9ba91ab8624e0660d4ea43f309bcb" [metadata.files] appdirs = [ @@ -415,6 +504,14 @@ apscheduler = [ {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -453,6 +550,10 @@ idna = [ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] itsdangerous = [ {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, @@ -525,6 +626,10 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, @@ -532,9 +637,17 @@ pathspec = [ peewee = [ {file = "peewee-3.14.4.tar.gz", hash = "sha256:9e356b327c2eaec6dd42ecea6f4ddded025793dba906a3d065a0452e726c51a2"}, ] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] profig = [ {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, ] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, @@ -543,9 +656,17 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, diff --git a/pyproject.toml b/pyproject.toml index fc9df59..2bfd9ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ rope = "^0.16.0" mypy = "^0.790" flake8-black = "^0.2.1" black = "^20.8b1" +pytest = "^6.2.4" [build-system] requires = ["poetry>=0.12"] From 4652c0e31c77b4691329e326a70630d9f81b55e0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:47:11 +0200 Subject: [PATCH 369/586] rename pytest workflow --- .github/workflows/{pytest => pytest.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{pytest => pytest.yml} (100%) diff --git a/.github/workflows/pytest b/.github/workflows/pytest.yml similarity index 100% rename from .github/workflows/pytest rename to .github/workflows/pytest.yml From a3c2036946c2d862abe9b0b37f8097cb84209c12 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:55:26 +0200 Subject: [PATCH 370/586] configure testing matrix (py version, OS) --- .github/workflows/pytest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 085426c..a771a6d 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: pytest +name: Run Pytests on: push jobs: @@ -6,8 +6,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8] - poetry-version: [1.0, 1.1.6] + python-version: [3.9, 3.9.6] + poetry-version: [1.1.7] os: [ubuntu-18.04, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: From 033718a13c935bdfe2163001eb03f28cafa58948 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:58:32 +0200 Subject: [PATCH 371/586] Install poetry deps --- .github/workflows/pytest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a771a6d..69d2e75 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -19,5 +19,7 @@ jobs: uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: ${{ matrix.poetry-version }} - - name: poetry pytest + - name: poetry install + run: poetry install + - name: poetry run pytest run: poetry run pytest From a01ae608f6676c763a21b1e9839332b432982a99 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 10 Aug 2021 20:40:22 +0200 Subject: [PATCH 372/586] add test coverage --- .github/workflows/pytest.yml | 4 +- poetry.lock | 87 +++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 69d2e75..56447c0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -21,5 +21,5 @@ jobs: poetry-version: ${{ matrix.poetry-version }} - name: poetry install run: poetry install - - name: poetry run pytest - run: poetry run pytest + - name: poetry run pytest and coverage + run: poetry run pytest --cov . diff --git a/poetry.lock b/poetry.lock index 4d42e97..29857a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -114,6 +114,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + [[package]] name = "flake8" version = "3.9.2" @@ -367,6 +378,22 @@ toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "2.12.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -493,7 +520,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e2969fb3de45c3e290504db65fd32ca958b9ba91ab8624e0660d4ea43f309bcb" +content-hash = "84e38d6533c1cc44cb0fd981a4b19b78b20d5f33e9c1f8d0aa2f60f73e91d4c4" [metadata.files] appdirs = [ @@ -531,6 +558,60 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +coverage = [ + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -667,6 +748,10 @@ pytest = [ {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] +pytest-cov = [ + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, diff --git a/pyproject.toml b/pyproject.toml index 2bfd9ab..5d36971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ mypy = "^0.790" flake8-black = "^0.2.1" black = "^20.8b1" pytest = "^6.2.4" +pytest-cov = "^2.12.1" [build-system] requires = ["poetry>=0.12"] From f1492ba217fb7b61f215970cad8367aaaf3b2fed Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:08:36 +0200 Subject: [PATCH 373/586] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15964de..c2b3297 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Python version badge](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version badge](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version badge](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) + ## Stacosys Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an humble alternative to comment hosting services like Disqus. Stacosys protects your readers's privacy. @@ -37,14 +39,14 @@ Stacosys can be hosted on the same server or on a different server than the blog *Which technologies are used?* -- [Python 3.9](https://www.python.org) +- [Python](https://www.python.org) - [Flask](http://flask.pocoo.org) - [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) ### Installation -Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases on GitHub](https://github.com/kianby/stacosys/releases) or the [Docker image](https://hub.docker.com/repository/docker/kianby/stacosys). +Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/repository/docker/kianby/stacosys). ### Improvements From 4ea23919a5ae15e202fa9baf4b8f62df8ea1fd2c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:17:28 +0200 Subject: [PATCH 374/586] Update pytest.yml --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 56447c0..977be6a 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: Run Pytests +name: pytest on: push jobs: From 1b478252d59233c85fd8abacbf2d4165074cfa7b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:17:46 +0200 Subject: [PATCH 375/586] Update docker.yml --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6069539..a03257e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Docker Image CI +name: docker on: push: branches: [ master ] From 103b8da6b3b1b083b3bac269db449f71c0b86c0c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:24:52 +0200 Subject: [PATCH 376/586] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2b3297..4592f37 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Python version badge](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version badge](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version badge](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) +[![Pytest badge](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Docker badge](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) + ## Stacosys Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an humble alternative to comment hosting services like Disqus. Stacosys protects your readers's privacy. @@ -46,7 +48,7 @@ Stacosys can be hosted on the same server or on a different server than the blog ### Installation -Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/repository/docker/kianby/stacosys). +Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/r/kianby/stacosys). ### Improvements From b14c7cee7b5733c4f20b977ad70ad016ac6e6e55 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:35:33 +0200 Subject: [PATCH 377/586] Update pytest.yml --- .github/workflows/pytest.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 977be6a..9e0c6e9 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -23,3 +23,7 @@ jobs: run: poetry install - name: poetry run pytest and coverage run: poetry run pytest --cov . + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} From b54a58d10d901756c582477c133bfeee5d208e75 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:39:14 +0200 Subject: [PATCH 378/586] Update pytest.yml --- .github/workflows/pytest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9e0c6e9..e46abc2 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,8 +22,9 @@ jobs: - name: poetry install run: poetry install - name: poetry run pytest and coverage - run: poetry run pytest --cov . + run: poetry run pytest --cov . --cov-report xml - name: Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.xml From 242044431b4624b8ef91b8ad013ce75cebbed625 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:43:21 +0200 Subject: [PATCH 379/586] Update pytest.yml --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index e46abc2..6182716 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,9 +22,9 @@ jobs: - name: poetry install run: poetry install - name: poetry run pytest and coverage - run: poetry run pytest --cov . --cov-report xml + run: poetry run pytest --cov . - name: Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: coverage.xml + From b3056966eaad06102724d30c5765e36dbdb87b43 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 14:53:43 +0200 Subject: [PATCH 380/586] Update pytest.yml --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 6182716..8e75d4e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,9 +22,9 @@ jobs: - name: poetry install run: poetry install - name: poetry run pytest and coverage - run: poetry run pytest --cov . + run: poetry run pytest --cov . --cov-report xml - name: Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - + path-to-lcov: ./coverage.xml From 7420528992e69eaa350d0c121f6b112646374f9e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 15:08:09 +0200 Subject: [PATCH 381/586] Update pytest.yml --- .github/workflows/pytest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 8e75d4e..48340b8 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,9 +22,9 @@ jobs: - name: poetry install run: poetry install - name: poetry run pytest and coverage - run: poetry run pytest --cov . --cov-report xml + run: poetry run pytest --cov . - name: Coveralls - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@1.1.3 with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./coverage.xml + path-to-lcov: .coverage From af23c4697db355d427d987384cab4168061bfbe6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 15:26:21 +0200 Subject: [PATCH 382/586] coverage --- .github/workflows/pytest.yml | 17 +++++++++-------- poetry.lock | 35 +++++++++++++++++++++++++++++++++-- pyproject.toml | 2 ++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 48340b8..6b4ba3b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -19,12 +19,13 @@ jobs: uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: ${{ matrix.poetry-version }} - - name: poetry install + - name: Install dependencies run: poetry install - - name: poetry run pytest and coverage - run: poetry run pytest --cov . - - name: Coveralls - uses: coverallsapp/github-action@1.1.3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: .coverage + - name: Pytest and Coverage + run: | + poetry run coverage run -m --source=stacosys pytest tests + poetry run coverage report + - name: Send report to Coveralls + run: poetry run coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/poetry.lock b/poetry.lock index 29857a0..db95a99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -118,13 +118,37 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] +[[package]] +name = "coveralls" +version = "3.2.0" +description = "Show coverage stats online via coveralls.io" +category = "dev" +optional = false +python-versions = ">= 3.5" + +[package.dependencies] +coverage = ">=4.1,<6.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "flake8" version = "3.9.2" @@ -520,7 +544,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "84e38d6533c1cc44cb0fd981a4b19b78b20d5f33e9c1f8d0aa2f60f73e91d4c4" +content-hash = "91de17883559ddc3bcac3bf247041b9d452b27d95115f62aeb13f5a6781f34e9" [metadata.files] appdirs = [ @@ -612,6 +636,13 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] +coveralls = [ + {file = "coveralls-3.2.0-py2.py3-none-any.whl", hash = "sha256:aedfcc5296b788ebaf8ace8029376e5f102f67c53d1373f2e821515c15b36527"}, + {file = "coveralls-3.2.0.tar.gz", hash = "sha256:15a987d9df877fff44cd81948c5806ffb6eafb757b3443f737888358e96156ee"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, diff --git a/pyproject.toml b/pyproject.toml index 5d36971..bb05880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ flask_apscheduler = "^1.11.0" Flask = "^2.0.1" peewee = "^3.14.0" requests = "^2.25.1" +coverage = "^5.5" [tool.poetry.dev-dependencies] rope = "^0.16.0" @@ -24,6 +25,7 @@ flake8-black = "^0.2.1" black = "^20.8b1" pytest = "^6.2.4" pytest-cov = "^2.12.1" +coveralls = "^3.2.0" [build-system] requires = ["poetry>=0.12"] From 2d58ca54f7e507b21fec6cfe75269124022bc1a8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 15:30:03 +0200 Subject: [PATCH 383/586] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4592f37..185f0a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Python version badge](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version badge](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version badge](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) +[![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) -[![Pytest badge](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Docker badge](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=master)](https://coveralls.io/github/kianby/stacosys?branch=master) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) ## Stacosys From 89c1b09c5719263cdd3d4ed5a8f6381882d115e8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Aug 2021 18:14:29 +0200 Subject: [PATCH 384/586] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 185f0a5..889d3ff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) +[![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) + [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) [![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=master)](https://coveralls.io/github/kianby/stacosys?branch=master) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) From f2c668425e4fe24b4a14cd601f3792f884d0d9ea Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Aug 2021 14:32:27 +0200 Subject: [PATCH 385/586] sonarlint --- stacosys/core/imap.py | 77 +++++++++++++++++++++---------------------- stacosys/db/dao.py | 6 ++-- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index cbd7105..2970e15 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -31,14 +31,14 @@ class Mailbox(object): self.imap.login(self.login, self.password) return self - def __exit__(self, type, value, traceback): + def __exit__(self, _type, value, traceback): self.imap.close() self.imap.logout() def get_count(self): self.imap.select("Inbox") _, data = self.imap.search(None, "ALL") - return sum(1 for num in data[0].split()) + return sum(1 for _ in data[0].split()) def fetch_raw_message(self, num): self.imap.select("Inbox") @@ -56,43 +56,26 @@ class Mailbox(object): if part.is_multipart(): continue - content_disposition = part.get("Content-Disposition", None) - if content_disposition: - # we have attachment - r = filename_re.findall(content_disposition) - if r: - filename = sorted(r[0])[1] - else: - filename = "undefined" - content = base64.b64encode(part.get_payload(decode=True)) - content = content.decode() - attachments.append( - Attachment( - filename=email_nonascii_to_uft8(filename), - content=content, - content_type=part.get_content_type(), - ) - ) + if _is_part_attachment(part): + attachments.append(_get_attachment(part)) else: try: - content = to_plain_text_content(part) + content = _to_plain_text_content(part) + parts.append( + Part(content=content, content_type=part.get_content_type()) + ) + if part.get_content_type() == "text/plain": + plain_text_content = content except Exception: logging.exception("cannot extract content from mail part") - parts.append( - Part(content=content, content_type=part.get_content_type()) - ) - - if part.get_content_type() == "text/plain": - plain_text_content = content - return Email( id=num, encoding="UTF-8", - date=parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), + date=_parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), from_addr=raw_msg["From"], to_addr=raw_msg["To"], - subject=email_nonascii_to_uft8(raw_msg["Subject"]), + subject=_email_non_ascii_to_uft8(raw_msg["Subject"]), parts=parts, attachments=attachments, plain_text_content=plain_text_content, @@ -118,26 +101,22 @@ class Mailbox(object): self.logger.debug("Message %s\n%s\n" % (num, data[0][1])) -def parse_date(v): +def _parse_date(v): if v is None: return datetime.datetime.now() - tt = email.utils.parsedate_tz(v) - if tt is None: return datetime.datetime.now() - timestamp = email.utils.mktime_tz(tt) date = datetime.datetime.fromtimestamp(timestamp) return date -def to_utf8(string, charset): +def _to_utf8(string, charset): return string.decode(charset).encode("UTF-8").decode("UTF-8") -def email_nonascii_to_uft8(string): - +def _email_non_ascii_to_uft8(string): # RFC 1342 is a recommendation that provides a way to represent non ASCII # characters inside e-mail in a way that won’t confuse e-mail servers subject = "" @@ -147,16 +126,36 @@ def email_nonascii_to_uft8(string): v = v.decode() subject = subject + v else: - subject = subject + to_utf8(v, charset) + subject = subject + _to_utf8(v, charset) return subject -def to_plain_text_content(part: Message) -> str: +def _to_plain_text_content(part: Message) -> str: content = part.get_payload(decode=True) charset = part.get_param("charset", None) if charset: - content = to_utf8(content, charset) + content = _to_utf8(content, charset) elif type(content) == bytes: content = content.decode("utf8") # RFC 3676: remove automatic word-wrapping return content.replace(" \r\n", " ") + + +def _is_part_attachment(part): + return part.get("Content-Disposition", None) + + +def _get_attachment(part) -> Attachment: + content_disposition = part.get("Content-Disposition", None) + r = filename_re.findall(content_disposition) + if r: + filename = sorted(r[0])[1] + else: + filename = "undefined" + content = base64.b64encode(part.get_payload(decode=True)) + content = content.decode() + return Attachment( + filename=_email_non_ascii_to_uft8(filename), + content=content, + content_type=part.get_content_type(), + ) diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 36ebdd4..0a14761 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -4,18 +4,20 @@ from datetime import datetime from stacosys.model.comment import Comment +TIME_FORMAT = "%Y-%m-%d %H:%M:%S" + def find_comment_by_id(id): return Comment.get_by_id(id) def notify_comment(comment: Comment): - comment.notified = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.notified = datetime.now().strftime(TIME_FORMAT) comment.save() def publish_comment(comment: Comment): - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + comment.published = datetime.now().strftime(TIME_FORMAT) comment.save() From 1854cf73e3d0fa172e9f0e59ab8479c1a2ceaecb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:58:10 +0200 Subject: [PATCH 386/586] first api test --- tests/test_api.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/test_api.py diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..25c299c --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import logging + +import pytest + +from stacosys.db import database +from stacosys.interface import api +from stacosys.interface import app + + +@pytest.fixture +def client(): + logger = logging.getLogger(__name__) + db = database.Database() + db.setup(":memory:") + app.config.update(SITE_TOKEN="ETC") + logger.info(f"start interface {api}") + return app.test_client() + + +def test_api_ping(client): + rv = client.get('/ping') + assert rv.data == b"OK" + + +def test_api_count(client): + rv = client.get('/comments/count') + assert b'{"count":0}\n' in rv.data From cd24661ed059e4fa0d8761cef9ccd86afeb92638 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 5 Sep 2021 17:14:08 +0200 Subject: [PATCH 387/586] cover api test --- tests/test_api.py | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 25c299c..9428f45 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,30 +1,65 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import json import logging import pytest -from stacosys.db import database +from stacosys.db import database, dao from stacosys.interface import api from stacosys.interface import app +def init_test_db(): + c1 = dao.create_comment("/site1", "Bob", "/bob.site", "", "comment 1") + c2 = dao.create_comment("/site2", "Bill", "/bill.site", "", "comment 2") + c3 = dao.create_comment("/site3", "Jack", "/jack.site", "", "comment 3") + dao.publish_comment(c1) + dao.publish_comment(c3) + + @pytest.fixture def client(): logger = logging.getLogger(__name__) db = database.Database() db.setup(":memory:") + init_test_db() app.config.update(SITE_TOKEN="ETC") logger.info(f"start interface {api}") return app.test_client() def test_api_ping(client): - rv = client.get('/ping') - assert rv.data == b"OK" + resp = client.get('/ping') + assert resp.data == b"OK" -def test_api_count(client): - rv = client.get('/comments/count') - assert b'{"count":0}\n' in rv.data +def test_api_count_global(client): + resp = client.get('/comments/count') + d = json.loads(resp.data) + assert d and d['count'] == 2 + + +def test_api_count_url(client): + resp = client.get('/comments/count?url=/site1') + d = json.loads(resp.data) + assert d and d['count'] == 1 + resp = client.get('/comments/count?url=/site2') + d = json.loads(resp.data) + assert d and d['count'] == 0 + + +def test_api_comment(client): + resp = client.get('/comments?url=/site1') + d = json.loads(resp.data) + assert d and len(d['data']) == 1 + comment = d['data'][0] + assert comment['author'] == 'Bob' + assert comment['content'] == 'comment 1' + + +def test_api_comment_not_found(client): + resp = client.get('/comments?url=/site2') + d = json.loads(resp.data) + assert d and d['data'] == [] From 86702625a08ac8047101f29347b161955c363da6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 10 Sep 2021 18:09:35 +0200 Subject: [PATCH 388/586] unit test form --- tests/test_form.py | 50 +++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/tests/test_form.py b/tests/test_form.py index f388c6a..2b4dfac 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -1,22 +1,40 @@ -import unittest +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import logging + +import pytest + +from stacosys.db import database +from stacosys.interface import app from stacosys.interface import form -class FormInterfaceTestCase(unittest.TestCase): - - def test_check_form_data_ok(self): - d = {"url": "/", "message": "", "site": "", "remarque": "", "author": "", "token": "", "email": ""} - self.assertTrue(form.check_form_data(d)) - d = {"url": "/"} - self.assertTrue(form.check_form_data(d)) - d = {} - self.assertTrue(form.check_form_data(d)) - - def test_check_form_data_ko(self): - d = {"url": "/", "message": "", "site": "", "remarque": "", "author": "", "token": "", "email": "", "bonus": ""} - self.assertFalse(form.check_form_data(d)) +@pytest.fixture +def client(): + logger = logging.getLogger(__name__) + db = database.Database() + db.setup(":memory:") + app.config.update(SITE_TOKEN="ETC") + logger.info(f"start interface {form}") + return app.test_client() -if __name__ == '__main__': - unittest.main() +def test_new_comment_honeypot(client): + resp = client.post('/newcomment', + content_type='multipart/form-data', + data={'remarque': 'trapped'}) + assert resp.status == '400 BAD REQUEST' + + +def test_new_comment_success(client): + resp = client.post('/newcomment', + content_type='multipart/form-data', + data={'author': 'Jack', 'url': '/site3', 'message': 'comment 3'}) + assert resp.status == '302 FOUND' + + +def test_check_form_data(): + from stacosys.interface.form import check_form_data + assert check_form_data({'author': 'Jack', 'url': '/site3', 'message': 'comment 3'}) + assert not check_form_data({'author': 'Jack', 'url': '/site3', 'message': 'comment 3', 'extra': 'ball'}) From 1827eedf5ccaff0631e6cfc1dcac144e62c70c4a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:26:00 +0100 Subject: [PATCH 389/586] CI --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a03257e..bbee4cd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,7 +1,7 @@ name: docker on: push: - branches: [ master ] + branches: [ main ] jobs: build: runs-on: ubuntu-latest From 601903d1d5389bd2929d0cf048576c7437bf29b1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:39:23 +0100 Subject: [PATCH 390/586] upgrade deps --- poetry.lock | 283 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 162 insertions(+), 121 deletions(-) diff --git a/poetry.lock b/poetry.lock index db95a99..f40b5c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "apscheduler" -version = "3.7.0" +version = "3.8.1" description = "In-process task scheduler with Cron-like capabilities" category = "main" optional = false @@ -17,7 +17,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0" +tzlocal = ">=2.0,<3.0.0 || >=4.0.0" [package.extras] asyncio = ["trollius"] @@ -78,7 +78,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2021.5.30" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -86,7 +86,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.3" +version = "2.0.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -97,7 +97,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.1" +version = "8.0.3" description = "Composable command line interface toolkit" category = "main" optional = false @@ -127,14 +127,14 @@ toml = ["toml"] [[package]] name = "coveralls" -version = "3.2.0" +version = "3.3.1" description = "Show coverage stats online via coveralls.io" category = "dev" optional = false python-versions = ">= 3.5" [package.dependencies] -coverage = ">=4.1,<6.0" +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -151,16 +151,16 @@ python-versions = "*" [[package]] name = "flake8" -version = "3.9.2" +version = "4.0.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "flake8-black" @@ -177,7 +177,7 @@ toml = "*" [[package]] name = "flask" -version = "2.0.1" +version = "2.0.2" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -208,7 +208,7 @@ python-dateutil = ">=2.4.2" [[package]] name = "idna" -version = "3.2" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -232,7 +232,7 @@ python-versions = ">=3.6" [[package]] name = "jinja2" -version = "3.0.1" +version = "3.0.3" description = "A very fast and expressive template engine." category = "main" optional = false @@ -297,26 +297,26 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.0" +version = "21.2" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3" [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "peewee" -version = "3.14.4" +version = "3.14.8" description = "a little orm" category = "main" optional = false @@ -324,14 +324,15 @@ python-versions = "*" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "profig" @@ -343,23 +344,23 @@ python-versions = "*" [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pyflakes" -version = "2.3.1" +version = "2.4.0" description = "passive checker of Python programs" category = "dev" optional = false @@ -383,7 +384,7 @@ python-versions = "*" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -395,7 +396,7 @@ attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -431,15 +432,26 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2021.1" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "main" optional = false python-versions = "*" +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + [[package]] name = "regex" -version = "2021.7.6" +version = "2021.11.10" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -500,26 +512,39 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false python-versions = "*" +[[package]] +name = "tzdata" +version = "2021.5" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + [[package]] name = "tzlocal" -version = "2.1" +version = "4.1" description = "tzinfo object for the local timezone" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -pytz = "*" +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] name = "urllib3" -version = "1.26.6" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -532,7 +557,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "werkzeug" -version = "2.0.1" +version = "2.0.2" description = "The comprehensive WSGI web application library." category = "main" optional = false @@ -552,8 +577,8 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] apscheduler = [ - {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, - {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, + {file = "APScheduler-3.8.1-py2.py3-none-any.whl", hash = "sha256:c22cb14b411a31435eb2c530dfbbec948ac63015b517087c7978adb61b574865"}, + {file = "APScheduler-3.8.1.tar.gz", hash = "sha256:5cf344ebcfbdaa48ae178c029c055cec7bc7a4a47c21e315e4d1f08bd35f2355"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, @@ -567,16 +592,16 @@ black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"}, - {file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"}, + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -637,30 +662,30 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] coveralls = [ - {file = "coveralls-3.2.0-py2.py3-none-any.whl", hash = "sha256:aedfcc5296b788ebaf8ace8029376e5f102f67c53d1373f2e821515c15b36527"}, - {file = "coveralls-3.2.0.tar.gz", hash = "sha256:15a987d9df877fff44cd81948c5806ffb6eafb757b3443f737888358e96156ee"}, + {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, + {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] flake8-black = [ {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, ] flask = [ - {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"}, - {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"}, + {file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"}, + {file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"}, ] flask-apscheduler = [ {file = "Flask-APScheduler-1.12.2.tar.gz", hash = "sha256:b9fe174b90d201d8beeba5522b023208f7bb6e2583fc02fea4be4bce5ee8f9e5"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -671,8 +696,8 @@ itsdangerous = [ {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, ] jinja2 = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, @@ -739,34 +764,34 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, + {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] peewee = [ - {file = "peewee-3.14.4.tar.gz", hash = "sha256:9e356b327c2eaec6dd42ecea6f4ddded025793dba906a3d065a0452e726c51a2"}, + {file = "peewee-3.14.8.tar.gz", hash = "sha256:01bd7f734defb08d7a3346a0c0ca7011bc8d0d685934ec0e001b3371d522ec53"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] profig = [ {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -776,8 +801,8 @@ pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, @@ -788,51 +813,63 @@ python-dateutil = [ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, +] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] regex = [ - {file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"}, - {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"}, - {file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"}, - {file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"}, - {file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"}, - {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"}, - {file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"}, - {file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"}, - {file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"}, - {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"}, - {file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"}, - {file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"}, - {file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"}, - {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"}, - {file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"}, - {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, - {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, + {file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"}, + {file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, + {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, + {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, + {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, + {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, + {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, + {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, + {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, + {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, + {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, + {file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, + {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, + {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, + {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, + {file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, + {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, + {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, + {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, @@ -884,19 +921,23 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] +tzdata = [ + {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, + {file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"}, ] tzlocal = [ - {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, - {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, + {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, + {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, ] urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] werkzeug = [ - {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, - {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, + {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, + {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, ] From 86217d36caa4e1578151c50c1370cc98f08c40c9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:52:16 +0100 Subject: [PATCH 391/586] README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 889d3ff..f0fd4f8 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,4 @@ Build is based on [Poetry](https://python-poetry.org/) but you can also use [pub ### Improvements Stacosys fits my needs and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork the project and enhance the project if you need more features. + From 427208ff8d43651a46ff6635e7602f34f91d5c93 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:00:02 +0100 Subject: [PATCH 392/586] install tzdata --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5a09258..30a6879 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.9-alpine ARG STACOSYS_VERSION=2.0 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl -RUN apk update && apk add bash && apk add wget && rm -rf /var/cache/apk/* +RUN apk update && apk add bash && apk add wget && apk add tzdata && rm -rf /var/cache/apk/* COPY docker/docker-init.sh /usr/local/bin/ RUN chmod +x usr/local/bin/docker-init.sh From 705becfcc01bf40714f0ea8e26bac9d72425ca68 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:09:06 +0100 Subject: [PATCH 393/586] alpine: configure timezone --- Dockerfile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 30a6879..5c1608b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,15 @@ FROM python:3.9-alpine ARG STACOSYS_VERSION=2.0 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl -RUN apk update && apk add bash && apk add wget && apk add tzdata && rm -rf /var/cache/apk/* +RUN apk update && apk add bash && apk add wget + +# Timezone +RUN apk add tzdata +RUN cp /usr/share/zoneinfo/Europe/Paris /etc/localtime +RUN echo "Europe/Paris" > /etc/timezone + +# Clean apk cache +RUN rm -rf /var/cache/apk/* COPY docker/docker-init.sh /usr/local/bin/ RUN chmod +x usr/local/bin/docker-init.sh From 53a7fa90cb775b8f378bcd105e9ae39b36ab9e09 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:40:12 +0100 Subject: [PATCH 394/586] upgrade poetry project --- .gitignore | 1 + poetry.lock | 143 +++++++++++++++++++++++++++++++++++++++++-------- pyproject.toml | 6 +-- 3 files changed, 126 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index b93037e..2f5cfda 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,5 @@ stacosys/lib64 workspace.code-workspace *.sqlite config-server.ini +config-dev.ini .idea/ diff --git a/poetry.lock b/poetry.lock index f40b5c1..be7fec3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -86,7 +86,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.7" +version = "2.0.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -214,6 +214,22 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "importlib-metadata" +version = "4.8.2" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + [[package]] name = "iniconfig" version = "1.1.1" @@ -246,12 +262,15 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.3.4" +version = "3.3.6" description = "Python implementation of Markdown." category = "main" optional = false python-versions = ">=3.6" +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + [package.extras] testing = ["coverage", "pyyaml"] @@ -297,14 +316,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.2" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2,<3" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" @@ -368,11 +387,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyrss2gen" @@ -512,11 +534,11 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "tzdata" @@ -566,10 +588,22 @@ python-versions = ">=3.6" [package.extras] watchdog = ["watchdog"] +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "91de17883559ddc3bcac3bf247041b9d452b27d95115f62aeb13f5a6781f34e9" +content-hash = "b54f622e5630967a5ee4078dedf7ad331cc9e3b064bfcd71c839583c002be28f" [metadata.files] appdirs = [ @@ -596,8 +630,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, - {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -687,6 +721,10 @@ idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] +importlib-metadata = [ + {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, + {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -700,16 +738,32 @@ jinja2 = [ {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] markdown = [ - {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, - {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, + {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, + {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -718,14 +772,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -735,6 +802,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -764,8 +837,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, - {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, @@ -794,8 +867,8 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, @@ -829,6 +902,11 @@ regex = [ {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"}, {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, @@ -838,6 +916,11 @@ regex = [ {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"}, {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, @@ -847,6 +930,11 @@ regex = [ {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"}, {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, @@ -857,6 +945,11 @@ regex = [ {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"}, {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, @@ -867,6 +960,11 @@ regex = [ {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"}, {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, @@ -921,9 +1019,8 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] tzdata = [ {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, @@ -941,3 +1038,7 @@ werkzeug = [ {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, ] +zipp = [ + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, +] diff --git a/pyproject.toml b/pyproject.toml index bb05880..616d0f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,9 @@ profig = "^0.5.1" markdown = "^3.1.1" flask_apscheduler = "^1.11.0" Flask = "^2.0.1" -peewee = "^3.14.0" requests = "^2.25.1" coverage = "^5.5" +peewee = "^3.14.8" [tool.poetry.dev-dependencies] rope = "^0.16.0" @@ -28,5 +28,5 @@ pytest-cov = "^2.12.1" coveralls = "^3.2.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From aac1e817bbc87f9531b857b22f529a528b34860e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:41:03 +0100 Subject: [PATCH 395/586] parametrize site redirect URL --- config.ini | 1 + run.py | 2 +- stacosys/conf/config.py | 1 + stacosys/interface/form.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.ini b/config.ini index 224b7e6..80f7e56 100755 --- a/config.ini +++ b/config.ini @@ -9,6 +9,7 @@ newcomment_polling = 60 name = "My blog" url = http://blog.mydomain.com admin_email = admin@mydomain.com +redirect = /redirect [http] host = 127.0.0.1 diff --git a/run.py b/run.py index 0d4b92c..735dfdf 100644 --- a/run.py +++ b/run.py @@ -105,7 +105,7 @@ def stacosys_server(config_pathname): ) # inject config parameters into flask - app.config.update(SITE_TOKEN=conf.get(ConfigParameter.SITE_TOKEN)) + app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) logger.info(f"start interfaces {api} {form}") # start Flask diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index e942bb2..9793aca 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -35,6 +35,7 @@ class ConfigParameter(Enum): SITE_URL = "site.url" SITE_TOKEN = "site.token" SITE_ADMIN_EMAIL = "site.admin_email" + SITE_REDIRECT = "site.redirect" class Config: diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index eb5b826..085c586 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -42,7 +42,7 @@ def new_form_comment(): # add a row to Comment table dao.create_comment(url, author_name, author_site, author_gravatar, message) - return redirect("/redirect/", code=302) + return redirect(app.config.get("SITE_REDIRECT"), code=302) def check_form_data(d): From 5386c82b0ec2f1c6907a020d4e04291a858313d4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:41:38 +0100 Subject: [PATCH 396/586] enforce inbox mngt --- stacosys/core/cron.py | 46 +++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index b4525e3..beed2ad 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -21,28 +21,36 @@ templater = Templater(template_path) def fetch_mail_answers(lang, mailer: Mailer, rss: Rss, site_token): - for msg in mailer.fetch(): - # filter stacosys e-mails - m = re.search(REGEX_EMAIL_SUBJECT, msg.subject, re.DOTALL) - if not m: - continue - - comment_id = int(m.group(1)) - submitted_token = m.group(2) - - # validate token - if submitted_token != site_token: - logger.warning("ignore corrupted email. Unknown token %d" % comment_id) - continue - - if not msg.plain_text_content: - logger.warning("ignore empty email") - continue - - _reply_comment_email(lang, mailer, rss, msg, comment_id) + while True: + msgs = mailer.fetch() + if len(msgs) == 0: + break + msg = msgs[0] + _process_answer_msg(msg, lang, mailer, rss, site_token) mailer.delete(msg.id) +def _process_answer_msg(msg, lang, mailer: Mailer, rss: Rss, site_token): + # filter stacosys e-mails + m = re.search(REGEX_EMAIL_SUBJECT, msg.subject, re.DOTALL) + if not m: + return + + comment_id = int(m.group(1)) + submitted_token = m.group(2) + + # validate token + if submitted_token != site_token: + logger.warning("ignore corrupted email. Unknown token %d" % comment_id) + return + + if not msg.plain_text_content: + logger.warning("ignore empty email") + return + + _reply_comment_email(lang, mailer, rss, msg, comment_id) + + def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, comment_id): # retrieve comment comment = dao.find_comment_by_id(comment_id) From f368033559d8b6b5c0d677ad5bbf70acbaf75f45 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:47:48 +0100 Subject: [PATCH 397/586] fix pytest. Bump version 2.1 --- Dockerfile | 2 +- pyproject.toml | 2 +- tests/test_form.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c1608b..a025b04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.9-alpine -ARG STACOSYS_VERSION=2.0 +ARG STACOSYS_VERSION=2.1 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget diff --git a/pyproject.toml b/pyproject.toml index 616d0f0..2af639c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.0" +version = "2.1" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" diff --git a/tests/test_form.py b/tests/test_form.py index 2b4dfac..94bea7e 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -15,7 +15,7 @@ def client(): logger = logging.getLogger(__name__) db = database.Database() db.setup(":memory:") - app.config.update(SITE_TOKEN="ETC") + app.config.update(SITE_REDIRECT="/redirect") logger.info(f"start interface {form}") return app.test_client() From 61d0891b475050861fdf5b6485915630ae993cb2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 5 Jan 2022 20:01:56 +0100 Subject: [PATCH 398/586] fix title decoding error. unknown-8bit charset --- stacosys/core/imap.py | 2 +- tests/test_imap.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/test_imap.py diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py index 2970e15..b53da1d 100755 --- a/stacosys/core/imap.py +++ b/stacosys/core/imap.py @@ -121,7 +121,7 @@ def _email_non_ascii_to_uft8(string): # characters inside e-mail in a way that won’t confuse e-mail servers subject = "" for v, charset in email.header.decode_header(string): - if charset is None: + if charset is None or charset == 'unknown-8bit': if type(v) is bytes: v = v.decode() subject = subject + v diff --git a/tests/test_imap.py b/tests/test_imap.py new file mode 100644 index 0000000..400ecbf --- /dev/null +++ b/tests/test_imap.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import os +import unittest + +from stacosys.core.imap import _to_utf8, _email_non_ascii_to_uft8 +from email.header import Header + + +class ImapTestCase(unittest.TestCase): + + def test_utf8_decode(self): + h = Header(s="Chez Darty vous avez re\udcc3\udca7u un nouvel aspirateur Vacuum gratuit jl8nz", + charset="unknown-8bit") + decoded = _email_non_ascii_to_uft8(h) + self.assertEquals(decoded, "Chez Darty vous avez reçu un nouvel aspirateur Vacuum gratuit jl8nz") From 21503bd711fcd7ac3789edacf459868bd5a0c695 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 5 Jan 2022 20:36:30 +0100 Subject: [PATCH 399/586] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f0fd4f8..889d3ff 100644 --- a/README.md +++ b/README.md @@ -54,4 +54,3 @@ Build is based on [Poetry](https://python-poetry.org/) but you can also use [pub ### Improvements Stacosys fits my needs and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork the project and enhance the project if you need more features. - From 47e51d6efa2c15573307fa46611fdae7d7eb50a2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jan 2022 17:42:30 +0100 Subject: [PATCH 400/586] warn assertEquals --- tests/test_imap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_imap.py b/tests/test_imap.py index 400ecbf..31aafd8 100644 --- a/tests/test_imap.py +++ b/tests/test_imap.py @@ -14,4 +14,4 @@ class ImapTestCase(unittest.TestCase): h = Header(s="Chez Darty vous avez re\udcc3\udca7u un nouvel aspirateur Vacuum gratuit jl8nz", charset="unknown-8bit") decoded = _email_non_ascii_to_uft8(h) - self.assertEquals(decoded, "Chez Darty vous avez reçu un nouvel aspirateur Vacuum gratuit jl8nz") + self.assertEqual(decoded, "Chez Darty vous avez reçu un nouvel aspirateur Vacuum gratuit jl8nz") From 4b10e30cc4b81b5cc9a69ff01982a135931d50e4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 8 Jan 2022 18:26:11 +0100 Subject: [PATCH 401/586] coverage use main branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 889d3ff..321fe82 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=master)](https://coveralls.io/github/kianby/stacosys?branch=master) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) ## Stacosys From 0fcc4111e769cfbbd76aa12de05cf49a4d6aa67d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jan 2022 08:43:01 +0100 Subject: [PATCH 402/586] add unit test --- tests/test_imap.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_imap.py b/tests/test_imap.py index 31aafd8..9528919 100644 --- a/tests/test_imap.py +++ b/tests/test_imap.py @@ -1,17 +1,22 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import os +import datetime import unittest - -from stacosys.core.imap import _to_utf8, _email_non_ascii_to_uft8 from email.header import Header +from stacosys.core import imap + class ImapTestCase(unittest.TestCase): def test_utf8_decode(self): h = Header(s="Chez Darty vous avez re\udcc3\udca7u un nouvel aspirateur Vacuum gratuit jl8nz", charset="unknown-8bit") - decoded = _email_non_ascii_to_uft8(h) + decoded = imap._email_non_ascii_to_uft8(h) self.assertEqual(decoded, "Chez Darty vous avez reçu un nouvel aspirateur Vacuum gratuit jl8nz") + + def test_parse_date(self): + now = datetime.datetime.now() + self.assertGreaterEqual(imap._parse_date(None), now) + self.assertEqual(imap._parse_date("Wed, 8 Dec 2021 20:05:20 +0100"), datetime.datetime(2021, 12, 8, 20, 5, 20)) From 9b3d78316823a356b7a4dcbbd0a8843202578b42 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jan 2022 10:51:59 +0100 Subject: [PATCH 403/586] don't handle timezone --- tests/test_imap.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_imap.py b/tests/test_imap.py index 9528919..0256b8a 100644 --- a/tests/test_imap.py +++ b/tests/test_imap.py @@ -19,4 +19,8 @@ class ImapTestCase(unittest.TestCase): def test_parse_date(self): now = datetime.datetime.now() self.assertGreaterEqual(imap._parse_date(None), now) - self.assertEqual(imap._parse_date("Wed, 8 Dec 2021 20:05:20 +0100"), datetime.datetime(2021, 12, 8, 20, 5, 20)) + parsed = imap._parse_date("Wed, 8 Dec 2021 20:05:20 +0100") + self.assertEqual(parsed.day, 8) + self.assertEqual(parsed.month, 12) + self.assertEqual(parsed.year, 2021) + # do not compare hours. don't care about timezone From 895efb5257981ffc8dafdebd5a66651360af5756 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 9 Jan 2022 11:04:28 +0100 Subject: [PATCH 404/586] add unit test --- tests/test_imap.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_imap.py b/tests/test_imap.py index 0256b8a..474fe85 100644 --- a/tests/test_imap.py +++ b/tests/test_imap.py @@ -4,6 +4,7 @@ import datetime import unittest from email.header import Header +from email.message import Message from stacosys.core import imap @@ -24,3 +25,9 @@ class ImapTestCase(unittest.TestCase): self.assertEqual(parsed.month, 12) self.assertEqual(parsed.year, 2021) # do not compare hours. don't care about timezone + + def test_to_plain_text_content(self): + msg = Message() + payload = b"non\r\n\r\nLe 08/12/2021 \xc3\xa0 20:04, kianby@free.fr a \xc3\xa9crit\xc2\xa0:\r\n> Bonjour,\r\n>\r\n> Un nouveau commentaire a \xc3\xa9t\xc3\xa9 post\xc3\xa9 pour l'article /2021/rester-discret-sur-github//\r\n>\r\n> Vous avez deux r\xc3\xa9ponses possibles :\r\n> - rejeter le commentaire en r\xc3\xa9pondant NO (ou no),\r\n> - accepter le commentaire en renvoyant cet email tel quel.\r\n>\r\n> Si cette derni\xc3\xa8re option est choisie, Stacosys publiera le commentaire tr\xc3\xa8s bient\xc3\xb4t.\r\n>\r\n> Voici les d\xc3\xa9tails concernant le commentaire :\r\n>\r\n> author: ET Rate\r\n> site:\r\n> date: 2021-12-08 20:03:58\r\n> url: /2021/rester-discret-sur-github//\r\n>\r\n> gfdgdgf\r\n>\r\n>\r\n> --\r\n> Stacosys\r\n" + msg.set_payload(payload, "UTF-8") + self.assertTrue(imap._to_plain_text_content(msg)) From 35a4be4ee6013eacb4493c247f75f9bebe52979b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jan 2022 17:04:16 +0100 Subject: [PATCH 405/586] use tox for local testing with multiple python versions --- poetry.lock | 104 ++++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 3 +- tox.ini | 9 +++++ 3 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 tox.ini diff --git a/poetry.lock b/poetry.lock index be7fec3..43ff91c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -141,6 +141,14 @@ requests = ">=1.0.0" [package.extras] yaml = ["PyYAML (>=3.10)"] +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "docopt" version = "0.6.2" @@ -149,6 +157,18 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "filelock" +version = "3.4.2" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + [[package]] name = "flake8" version = "4.0.1" @@ -318,7 +338,7 @@ python-versions = "*" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -341,11 +361,23 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "platformdirs" +version = "2.4.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -365,7 +397,7 @@ python-versions = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -389,7 +421,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pyparsing" version = "3.0.6" description = "Python parsing module" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -520,10 +552,32 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tox" +version = "3.24.5" +description = "tox is a generic virtualenv management and test command line tool" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] + [[package]] name = "typed-ast" version = "1.4.3" @@ -577,6 +631,24 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "virtualenv" +version = "20.13.0" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + [[package]] name = "werkzeug" version = "2.0.2" @@ -603,7 +675,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "b54f622e5630967a5ee4078dedf7ad331cc9e3b064bfcd71c839583c002be28f" +content-hash = "15798b3b3958a5650ed91f13d02450c2fcc0caebeb2fd23c79c6a63e7244aa39" [metadata.files] appdirs = [ @@ -699,9 +771,17 @@ coveralls = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] +filelock = [ + {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, + {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, +] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, @@ -847,6 +927,10 @@ pathspec = [ peewee = [ {file = "peewee-3.14.8.tar.gz", hash = "sha256:01bd7f734defb08d7a3346a0c0ca7011bc8d0d685934ec0e001b3371d522ec53"}, ] +platformdirs = [ + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -986,6 +1070,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tox = [ + {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, + {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, +] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, @@ -1034,6 +1122,10 @@ urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] +virtualenv = [ + {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, + {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, +] werkzeug = [ {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, diff --git a/pyproject.toml b/pyproject.toml index 2af639c..64d6f78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" include = ["run.py"] [tool.poetry.dependencies] -python = "^3.9" +python = "^3.8" apscheduler = "^3.6.3" pyrss2gen = "^1.1" profig = "^0.5.1" @@ -17,6 +17,7 @@ Flask = "^2.0.1" requests = "^2.25.1" coverage = "^5.5" peewee = "^3.14.8" +tox = "^3.24.5" [tool.poetry.dev-dependencies] rope = "^0.16.0" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..c4ac41c --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +isolated_build = true +envlist = py38, py39 + +[testenv] +whitelist_externals = poetry +commands = + poetry install -v + poetry run pytest tests/ From e70611aa574c1bfaacbb96681014480da01114d2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jan 2022 17:08:41 +0100 Subject: [PATCH 406/586] compatible with python 3.8 --- .github/workflows/pytest.yml | 4 +- poetry.lock | 245 ++++++++++++++++++++--------------- 2 files changed, 140 insertions(+), 109 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 6b4ba3b..bdf03a8 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -6,8 +6,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, 3.9.6] - poetry-version: [1.1.7] + python-version: [3.8.12, 3.9.10] + poetry-version: [1.1.12] os: [ubuntu-18.04, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/poetry.lock b/poetry.lock index 43ff91c..c305e46 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,17 +42,28 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "backports.zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +tzdata = ["tzdata"] [[package]] name = "black" @@ -86,7 +97,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.9" +version = "2.0.10" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -215,7 +226,7 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-apscheduler" -version = "1.12.2" +version = "1.12.3" description = "Adds APScheduler support to Flask" category = "main" optional = false @@ -236,11 +247,11 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.8.2" +version = "4.10.1" description = "Read metadata from Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] zipp = ">=0.5" @@ -248,7 +259,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -419,7 +430,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" -version = "3.0.6" +version = "3.0.7" description = "Python parsing module" category = "main" optional = false @@ -501,11 +512,12 @@ optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" [package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} tzdata = {version = "*", markers = "python_version >= \"3.6\""} [[package]] name = "regex" -version = "2021.11.10" +version = "2022.1.18" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -513,7 +525,7 @@ python-versions = "*" [[package]] name = "requests" -version = "2.26.0" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -611,6 +623,7 @@ optional = false python-versions = ">=3.6" [package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} pytz-deprecation-shim = "*" tzdata = {version = "*", markers = "platform_system == \"Windows\""} @@ -620,7 +633,7 @@ test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -662,20 +675,20 @@ watchdog = ["watchdog"] [[package]] name = "zipp" -version = "3.6.0" +version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" -python-versions = "^3.9" -content-hash = "15798b3b3958a5650ed91f13d02450c2fcc0caebeb2fd23c79c6a63e7244aa39" +python-versions = "^3.8" +content-hash = "79d96a01febb3d55127734be5b86aefdaab09d1f400efd8cbbcd55faf00030cb" [metadata.files] appdirs = [ @@ -691,8 +704,26 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +"backports.zoneinfo" = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, @@ -702,8 +733,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, - {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, + {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, + {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -795,15 +826,15 @@ flask = [ {file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"}, ] flask-apscheduler = [ - {file = "Flask-APScheduler-1.12.2.tar.gz", hash = "sha256:b9fe174b90d201d8beeba5522b023208f7bb6e2583fc02fea4be4bce5ee8f9e5"}, + {file = "Flask-APScheduler-1.12.3.tar.gz", hash = "sha256:d60948d1f2be9eb4772f68c3308ba3f973755219d13947266f89292ad6df63fc"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, - {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, + {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, + {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -951,8 +982,8 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pyparsing = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, @@ -978,84 +1009,84 @@ pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] regex = [ - {file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"}, - {file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, - {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"}, - {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"}, - {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"}, - {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"}, - {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"}, - {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, - {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, - {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, - {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"}, - {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"}, - {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"}, - {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"}, - {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"}, - {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, - {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, - {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, - {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"}, - {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"}, - {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"}, - {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"}, - {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"}, - {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, - {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, - {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, - {file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, - {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"}, - {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"}, - {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"}, - {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"}, - {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"}, - {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, - {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, - {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, - {file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, - {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"}, - {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"}, - {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"}, - {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"}, - {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"}, - {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, - {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, - {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, + {file = "regex-2022.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5"}, + {file = "regex-2022.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7"}, + {file = "regex-2022.1.18-cp310-cp310-win32.whl", hash = "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d"}, + {file = "regex-2022.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184"}, + {file = "regex-2022.1.18-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a"}, + {file = "regex-2022.1.18-cp36-cp36m-win32.whl", hash = "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3"}, + {file = "regex-2022.1.18-cp36-cp36m-win_amd64.whl", hash = "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633"}, + {file = "regex-2022.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a"}, + {file = "regex-2022.1.18-cp37-cp37m-win32.whl", hash = "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6"}, + {file = "regex-2022.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d"}, + {file = "regex-2022.1.18-cp38-cp38-win32.whl", hash = "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02"}, + {file = "regex-2022.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093"}, + {file = "regex-2022.1.18-cp39-cp39-win32.whl", hash = "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1"}, + {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, + {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rope = [ {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, @@ -1119,8 +1150,8 @@ tzlocal = [ {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, @@ -1131,6 +1162,6 @@ werkzeug = [ {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] From ccf0b2b688bbfab8e050ae8bd576bc1e5e348cb0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jan 2022 17:11:46 +0100 Subject: [PATCH 407/586] downgrade because latest minor releases are not yet available --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index bdf03a8..ff50a69 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8.12, 3.9.10] + python-version: [3.8.10, 3.9.9] poetry-version: [1.1.12] os: [ubuntu-18.04, macos-latest, windows-latest] runs-on: ${{ matrix.os }} From 865472af51a6760cf97ffb71fc6d289985dbc1df Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jan 2022 18:22:23 +0100 Subject: [PATCH 408/586] prepare migration script to add ulid column --- dbmigration/migrate_from_2.1_to_3.0.py | 38 ++++++++++++++++++++++++++ poetry.lock | 14 +++++++++- pyproject.toml | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 dbmigration/migrate_from_2.1_to_3.0.py diff --git a/dbmigration/migrate_from_2.1_to_3.0.py b/dbmigration/migrate_from_2.1_to_3.0.py new file mode 100644 index 0000000..e421d6e --- /dev/null +++ b/dbmigration/migrate_from_2.1_to_3.0.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import sqlite3 +import datetime +from ulid import ULID + +# add column ulid +connection = sqlite3.connect("db.sqlite") +cursor = connection.cursor() +script = """ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; +ALTER TABLE comment ADD ulid INTEGER; +COMMIT; +PRAGMA foreign_keys = ON; +""" +cursor.executescript(script) +connection.close() + +# fill in ulid column +connection = sqlite3.connect("db.sqlite") +cursor = connection.cursor() +updates = [] +for row in cursor.execute('SELECT * FROM comment'): + row_id = row[0] + string_created = row[2] + date_created = datetime.datetime.strptime(string_created, "%Y-%m-%d %H:%M:%S") + ulid = ULID.from_datetime(date_created) + update = "UPDATE comment SET ulid = " + str(int(ulid)) + " WHERE id = " + str(row_id) + print(update) + updates.append(update) + +for update in updates: + pass + connection.execute(update) +connection.commit() +connection.close() diff --git a/poetry.lock b/poetry.lock index c305e46..70cb658 100644 --- a/poetry.lock +++ b/poetry.lock @@ -495,6 +495,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-ulid" +version = "1.0.3" +description = "Universally Unique Lexicographically Sortable Identifier" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pytz" version = "2021.3" @@ -688,7 +696,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "79d96a01febb3d55127734be5b86aefdaab09d1f400efd8cbbcd55faf00030cb" +content-hash = "24b77862cfbece0c68447f4d026bed51431e3a655a92dde2697368e758bfac89" [metadata.files] appdirs = [ @@ -1000,6 +1008,10 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-ulid = [ + {file = "python-ulid-1.0.3.tar.gz", hash = "sha256:5dd8b969312a40e2212cec9c1ad63f25d4b6eafd92ee3195883e0287b6e9d19e"}, + {file = "python_ulid-1.0.3-py3-none-any.whl", hash = "sha256:8704dc20f547f531fe3a41d4369842d737a0f275403b909d0872e7ea0fe8d6f2"}, +] pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, diff --git a/pyproject.toml b/pyproject.toml index 64d6f78..718ebee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ requests = "^2.25.1" coverage = "^5.5" peewee = "^3.14.8" tox = "^3.24.5" +python-ulid = "^1.0.3" [tool.poetry.dev-dependencies] rope = "^0.16.0" From 9352ca665d7c7c7aceb6cd8e87e6fdfcb3353f59 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 25 Jan 2022 07:12:59 +0100 Subject: [PATCH 409/586] init script to create an empty db --- dbmigration/create_empty_db.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dbmigration/create_empty_db.py diff --git a/dbmigration/create_empty_db.py b/dbmigration/create_empty_db.py new file mode 100644 index 0000000..8bb64ac --- /dev/null +++ b/dbmigration/create_empty_db.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import sqlite3 + +connection = sqlite3.connect("db.sqlite") +cursor = connection.cursor() + +# What script performs: +# - first, remove site table: crash here if table doesn't exist (compatibility test without effort) +# - remove site_id colum from comment table +script = """ +CREATE TABLE comment ( + id INTEGER NOT NULL PRIMARY KEY, + url VARCHAR(255) NOT NULL, + notified DATETIME, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, + author_site VARCHAR(255) NOT NULL, + author_gravatar varchar(255), + content TEXT NOT NULL +, ulid INTEGER); +""" + +cursor.executescript(script) +connection.close() \ No newline at end of file From 52b962b0c608d37da54a3a80850fbbb241dab8ad Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 25 Jan 2022 07:52:56 +0100 Subject: [PATCH 410/586] draft web admin page --- run.py | 3 +- stacosys/core/templater.py | 1 + stacosys/interface/webadmin.py | 28 +++++++++++++ .../templates/fr/web_comment_approval.tpl | 41 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 stacosys/interface/webadmin.py create mode 100644 stacosys/templates/fr/web_comment_approval.tpl diff --git a/run.py b/run.py index 735dfdf..520e9ac 100644 --- a/run.py +++ b/run.py @@ -14,6 +14,7 @@ from stacosys.core.mailer import Mailer from stacosys.interface import app from stacosys.interface import api from stacosys.interface import form +from stacosys.interface import webadmin from stacosys.interface import scheduler @@ -106,7 +107,7 @@ def stacosys_server(config_pathname): # inject config parameters into flask app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) - logger.info(f"start interfaces {api} {form}") + logger.info(f"start interfaces {api} {form} {webadmin}") # start Flask app.run( diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py index d3d4564..9b00964 100644 --- a/stacosys/core/templater.py +++ b/stacosys/core/templater.py @@ -11,6 +11,7 @@ class Template(Enum): NEW_COMMENT = "new_comment" NOTIFY_MESSAGE = "notify_message" RSS_TITLE_MESSAGE = "rss_title_message" + WEB_COMMENT_APPROVAL = "web_comment_approval" class Templater: diff --git a/stacosys/interface/webadmin.py b/stacosys/interface/webadmin.py new file mode 100644 index 0000000..5f0ca50 --- /dev/null +++ b/stacosys/interface/webadmin.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import os +from stacosys.core.templater import Templater, Template + +from flask import jsonify, request +from flask import render_template + +from stacosys.db import dao +from stacosys.interface import app + +logger = logging.getLogger(__name__) + + +current_path = os.path.dirname(__file__) +template_path = os.path.abspath(os.path.join(current_path, "../templates")) +templater = Templater(template_path) + +@app.route("/web/comment", methods=["GET"]) +def web_comment_approval(): + lang = "fr" + return templater.get_template(lang, Template.WEB_COMMENT_APPROVAL).render( + name="Yax") + + + diff --git a/stacosys/templates/fr/web_comment_approval.tpl b/stacosys/templates/fr/web_comment_approval.tpl new file mode 100644 index 0000000..9f16c78 --- /dev/null +++ b/stacosys/templates/fr/web_comment_approval.tpl @@ -0,0 +1,41 @@ + + + + + +Stacosys + + + +
    +

    Modération des commentaires

    + +
    +
    + + + + + + + + + + + + + + + + + +
    AuteurCommentaireArticleActions
    GégéMerci pour ce commentaire ! Malheureusement je n'ai pas utilisé Isso suffisamment longtemps pour être en capacité de vous aider avec MathJax. Suite à cet [évènement](https://blogduyax.madyanne.fr/2019/je-vous-lavais-bien-dit/) je suis revenu à [mon ancestral système de commentaires](https://blogduyax.madyanne.fr/2020/bilan-hebergement-2020/)./2019/refonte-complete-du-blog/ + + +
    +
    +
    +

    Cette page a été conçue par Yax avec Simple.css.

    +
    + + \ No newline at end of file From 9b2c14e3a0476bf2cb1125d706e868cb978036a0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 30 Jan 2022 18:56:52 +0100 Subject: [PATCH 411/586] draft web admin: no action, no security --- run.py | 18 +++---- stacosys/db/dao.py | 4 ++ stacosys/interface/__init__.py | 3 ++ stacosys/interface/templates/admin_fr.html | 61 ++++++++++++++++++++++ stacosys/interface/web/admin.py | 24 +++++++++ stacosys/interface/webadmin.py | 28 ---------- 6 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 stacosys/interface/templates/admin_fr.html create mode 100644 stacosys/interface/web/admin.py delete mode 100644 stacosys/interface/webadmin.py diff --git a/run.py b/run.py index 520e9ac..3608da2 100644 --- a/run.py +++ b/run.py @@ -1,21 +1,21 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import sys -import os import argparse -import logging import hashlib +import logging +import os +import sys from stacosys.conf.config import Config, ConfigParameter -from stacosys.db import database -from stacosys.core.rss import Rss from stacosys.core.mailer import Mailer -from stacosys.interface import app +from stacosys.core.rss import Rss +from stacosys.db import database from stacosys.interface import api +from stacosys.interface import app from stacosys.interface import form -from stacosys.interface import webadmin from stacosys.interface import scheduler +from stacosys.interface.web import admin # configure logging @@ -33,7 +33,6 @@ def configure_logging(level): def stacosys_server(config_pathname): - # configure logging logger = logging.getLogger(__name__) configure_logging(logging.INFO) @@ -107,7 +106,8 @@ def stacosys_server(config_pathname): # inject config parameters into flask app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) - logger.info(f"start interfaces {api} {form} {webadmin}") + app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) + logger.info(f"start interfaces {api} {form} {admin}") # start Flask app.run( diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 0a14761..e24fec2 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -29,6 +29,10 @@ def find_not_notified_comments(): return Comment.select().where(Comment.notified.is_null()) +def find_not_published_comments(): + return Comment.select().where(Comment.published.is_null()) + + def find_published_comments_by_url(url): return Comment.select(Comment).where((Comment.url == url) & (Comment.published.is_null(False))).order_by( +Comment.published) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index 1fab892..ee1c258 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -4,3 +4,6 @@ from flask import Flask app = Flask(__name__) + +# Set the secret key to some random bytes. Keep this really secret! +app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' \ No newline at end of file diff --git a/stacosys/interface/templates/admin_fr.html b/stacosys/interface/templates/admin_fr.html new file mode 100644 index 0000000..ee77e35 --- /dev/null +++ b/stacosys/interface/templates/admin_fr.html @@ -0,0 +1,61 @@ + + + + + +Stacosys + + + +
    +

    Modération des commentaires

    +
    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +

    {{ message }}

    + {% endfor %} +
    + {% endif %} + {% endwith %} + + + + + + + + + + + + {% for comment in comments %} + + + + + + + + {% endfor %} + +
    DateAuteurCommentaireArticleActions
    {{ comment.created }}{{ comment.author_name }}{{ comment.content }}{{ comment.url }} +
    + + + +
    +
    + + + +
    +
    +
    +
    +

    Cette page a été conçue par Yax avec Simple.css.

    +
    + + \ No newline at end of file diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py new file mode 100644 index 0000000..29f97f2 --- /dev/null +++ b/stacosys/interface/web/admin.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging + +from flask import request, redirect, flash, render_template + +from stacosys.db import dao +from stacosys.interface import app + +logger = logging.getLogger(__name__) + + +@app.route("/web", methods=["GET"]) +def admin_homepage(): + lang = "fr" + comments = dao.find_not_published_comments() + return render_template("admin_" + lang + ".html", comments=comments, baseurl=app.config.get("SITE_URL")) + + +@app.route("/web", methods=["POST"]) +def admin_action(): + flash(request.form.get("comment") + " " + request.form.get("action")) + return redirect('/web') diff --git a/stacosys/interface/webadmin.py b/stacosys/interface/webadmin.py deleted file mode 100644 index 5f0ca50..0000000 --- a/stacosys/interface/webadmin.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -import os -from stacosys.core.templater import Templater, Template - -from flask import jsonify, request -from flask import render_template - -from stacosys.db import dao -from stacosys.interface import app - -logger = logging.getLogger(__name__) - - -current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) -templater = Templater(template_path) - -@app.route("/web/comment", methods=["GET"]) -def web_comment_approval(): - lang = "fr" - return templater.get_template(lang, Template.WEB_COMMENT_APPROVAL).render( - name="Yax") - - - From 91b4dc9e2a5c1c4bb5063d1836634e94cdc435e3 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 30 Jan 2022 18:57:39 +0100 Subject: [PATCH 412/586] prefix api endpoints --- stacosys/interface/api.py | 6 +++--- tests/test_api.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index cab2905..042f0fc 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -11,12 +11,12 @@ from stacosys.interface import app logger = logging.getLogger(__name__) -@app.route("/ping", methods=["GET"]) +@app.route("/api/ping", methods=["GET"]) def ping(): return "OK" -@app.route("/comments", methods=["GET"]) +@app.route("/api/comments", methods=["GET"]) def query_comments(): comments = [] url = request.args.get("url", "") @@ -36,7 +36,7 @@ def query_comments(): return jsonify({"data": comments}) -@app.route("/comments/count", methods=["GET"]) +@app.route("/api/comments/count", methods=["GET"]) def get_comments_count(): url = request.args.get("url", "") return jsonify({"count": dao.count_published_comments(url)}) diff --git a/tests/test_api.py b/tests/test_api.py index 9428f45..b162153 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -31,27 +31,27 @@ def client(): def test_api_ping(client): - resp = client.get('/ping') + resp = client.get('/api/ping') assert resp.data == b"OK" def test_api_count_global(client): - resp = client.get('/comments/count') + resp = client.get('/api/comments/count') d = json.loads(resp.data) assert d and d['count'] == 2 def test_api_count_url(client): - resp = client.get('/comments/count?url=/site1') + resp = client.get('/api/comments/count?url=/site1') d = json.loads(resp.data) assert d and d['count'] == 1 - resp = client.get('/comments/count?url=/site2') + resp = client.get('/api/comments/count?url=/site2') d = json.loads(resp.data) assert d and d['count'] == 0 def test_api_comment(client): - resp = client.get('/comments?url=/site1') + resp = client.get('/api/comments?url=/site1') d = json.loads(resp.data) assert d and len(d['data']) == 1 comment = d['data'][0] @@ -60,6 +60,6 @@ def test_api_comment(client): def test_api_comment_not_found(client): - resp = client.get('/comments?url=/site2') + resp = client.get('/api/comments?url=/site2') d = json.loads(resp.data) assert d and d['data'] == [] From 392c6bc748afd461aa5aa023959bd22ca34b977e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 30 Jan 2022 19:07:22 +0100 Subject: [PATCH 413/586] localize web template --- run.py | 1 + stacosys/interface/web/admin.py | 4 +- .../templates/fr/web_comment_approval.tpl | 41 ------------------- 3 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 stacosys/templates/fr/web_comment_approval.tpl diff --git a/run.py b/run.py index 3608da2..b1b3fa9 100644 --- a/run.py +++ b/run.py @@ -107,6 +107,7 @@ def stacosys_server(config_pathname): # inject config parameters into flask app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) + app.config.update(LANG=conf.get(ConfigParameter.LANG)) logger.info(f"start interfaces {api} {form} {admin}") # start Flask diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index 29f97f2..df77ba9 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -13,9 +13,9 @@ logger = logging.getLogger(__name__) @app.route("/web", methods=["GET"]) def admin_homepage(): - lang = "fr" comments = dao.find_not_published_comments() - return render_template("admin_" + lang + ".html", comments=comments, baseurl=app.config.get("SITE_URL")) + return render_template("admin_" + app.config.get("LANG") + ".html", comments=comments, + baseurl=app.config.get("SITE_URL")) @app.route("/web", methods=["POST"]) diff --git a/stacosys/templates/fr/web_comment_approval.tpl b/stacosys/templates/fr/web_comment_approval.tpl deleted file mode 100644 index 9f16c78..0000000 --- a/stacosys/templates/fr/web_comment_approval.tpl +++ /dev/null @@ -1,41 +0,0 @@ - - - - - -Stacosys - - - -
    -

    Modération des commentaires

    - -
    -
    - - - - - - - - - - - - - - - - - -
    AuteurCommentaireArticleActions
    GégéMerci pour ce commentaire ! Malheureusement je n'ai pas utilisé Isso suffisamment longtemps pour être en capacité de vous aider avec MathJax. Suite à cet [évènement](https://blogduyax.madyanne.fr/2019/je-vous-lavais-bien-dit/) je suis revenu à [mon ancestral système de commentaires](https://blogduyax.madyanne.fr/2020/bilan-hebergement-2020/)./2019/refonte-complete-du-blog/ - - -
    -
    -
    -

    Cette page a été conçue par Yax avec Simple.css.

    -
    - - \ No newline at end of file From 67262ec7855950fb04f770e0e84721a2b1155966 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 30 Jan 2022 20:11:34 +0100 Subject: [PATCH 414/586] add simple login page --- stacosys/interface/templates/admin_fr.html | 13 ++++--- stacosys/interface/templates/login_fr.html | 42 ++++++++++++++++++++++ stacosys/interface/web/admin.py | 35 +++++++++++++++--- 3 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 stacosys/interface/templates/login_fr.html diff --git a/stacosys/interface/templates/admin_fr.html b/stacosys/interface/templates/admin_fr.html index ee77e35..bfe3221 100644 --- a/stacosys/interface/templates/admin_fr.html +++ b/stacosys/interface/templates/admin_fr.html @@ -8,10 +8,13 @@
    -

    Modération des commentaires

    +

    Modération des commentaires

    +
    - {% with messages = get_flashed_messages() %} + {% with messages = get_flashed_messages() %} {% if messages %}
    {% for message in messages %} @@ -19,7 +22,7 @@ {% endfor %}
    {% endif %} - {% endwith %} + {% endwith %} @@ -38,12 +41,12 @@
    {{ comment.content }} {{ comment.url }} -
    +
    -
    + diff --git a/stacosys/interface/templates/login_fr.html b/stacosys/interface/templates/login_fr.html new file mode 100644 index 0000000..e43d951 --- /dev/null +++ b/stacosys/interface/templates/login_fr.html @@ -0,0 +1,42 @@ + + + + + +Stacosys + + + + +
    +

    Modération des commentaires

    +
    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +

    {{ message }}

    + {% endfor %} +
    + {% endif %} + {% endwith %} + +

    +

    +

    +

    + + +
    +
    +

    Cette page a été conçue par Yax avec Simple.css.

    +
    + + diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index df77ba9..32104e5 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -3,22 +3,49 @@ import logging -from flask import request, redirect, flash, render_template +from flask import request, redirect, flash, render_template, session from stacosys.db import dao from stacosys.interface import app logger = logging.getLogger(__name__) +user = {"username": "admin", "password": "toto"} -@app.route("/web", methods=["GET"]) + +@app.route('/web/login', methods=['POST', 'GET']) +def login(): + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + if username == user['username'] and password == user['password']: + session['user'] = username + return redirect('/web/admin') + + flash("Identifiant ou mot de passe incorrect") + return redirect('/web/login') + + return render_template("login_" + app.config.get("LANG") + ".html") + + +@app.route('/web/logout', methods=["GET"]) +def logout(): + session.pop('user') + return redirect('/web/login') + + +@app.route("/web/admin", methods=["GET"]) def admin_homepage(): + if not ('user' in session and session['user'] == user['username']): + flash("Vous avez été déconnecté.") + return redirect('/web/login') + comments = dao.find_not_published_comments() return render_template("admin_" + app.config.get("LANG") + ".html", comments=comments, baseurl=app.config.get("SITE_URL")) -@app.route("/web", methods=["POST"]) +@app.route("/web/admin", methods=["POST"]) def admin_action(): flash(request.form.get("comment") + " " + request.form.get("action")) - return redirect('/web') + return redirect('/web/admin') From 2e74425108da1c8913f2400eeebcc6301026a8c1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:56:18 +0100 Subject: [PATCH 415/586] add config for web login --- config.ini | 5 +++++ run.py | 2 ++ stacosys/conf/config.py | 3 +++ stacosys/interface/web/admin.py | 10 +++++++--- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/config.ini b/config.ini index 80f7e56..fda705f 100755 --- a/config.ini +++ b/config.ini @@ -34,3 +34,8 @@ ssl = false port = 587 login = blog@mydomain.com password = MYPASSWORD + +[web] +username = admin +; SHA-256 hashed password (https://coding.tools/sha256) +password = 8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918 diff --git a/run.py b/run.py index b1b3fa9..03cb94b 100644 --- a/run.py +++ b/run.py @@ -108,6 +108,8 @@ def stacosys_server(config_pathname): app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) app.config.update(LANG=conf.get(ConfigParameter.LANG)) + app.config.update(WEB_USERNAME=conf.get(ConfigParameter.WEB_USERNAME)) + app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) logger.info(f"start interfaces {api} {form} {admin}") # start Flask diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 9793aca..cb68c0b 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -37,6 +37,9 @@ class ConfigParameter(Enum): SITE_ADMIN_EMAIL = "site.admin_email" SITE_REDIRECT = "site.redirect" + WEB_USERNAME = "web.username" + WEB_PASSWORD = "web.password" + class Config: def __init__(self): diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index 32104e5..c4d7a3c 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import hashlib import logging from flask import request, redirect, flash, render_template, session @@ -10,7 +11,10 @@ from stacosys.interface import app logger = logging.getLogger(__name__) -user = {"username": "admin", "password": "toto"} + +def is_login_ok(username, password): + hashed = hashlib.sha256(password.encode()).hexdigest().upper() + return app.config.get("WEB_USERNAME") == username and app.config.get("WEB_PASSWORD") == hashed @app.route('/web/login', methods=['POST', 'GET']) @@ -18,7 +22,7 @@ def login(): if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') - if username == user['username'] and password == user['password']: + if is_login_ok(username, password): session['user'] = username return redirect('/web/admin') @@ -36,7 +40,7 @@ def logout(): @app.route("/web/admin", methods=["GET"]) def admin_homepage(): - if not ('user' in session and session['user'] == user['username']): + if not ('user' in session and session['user'] == app.config.get("WEB_USERNAME")): flash("Vous avez été déconnecté.") return redirect('/web/login') From 1ae37ff18eef3608f722ec70339e191ad976a198 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Feb 2022 21:23:30 +0100 Subject: [PATCH 416/586] route admin --- run.py | 4 ++-- stacosys/interface/web/admin.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index 03cb94b..df5517d 100644 --- a/run.py +++ b/run.py @@ -105,9 +105,9 @@ def stacosys_server(config_pathname): ) # inject config parameters into flask - app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) - app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) app.config.update(LANG=conf.get(ConfigParameter.LANG)) + app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) + app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) app.config.update(WEB_USERNAME=conf.get(ConfigParameter.WEB_USERNAME)) app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) logger.info(f"start interfaces {api} {form} {admin}") diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index c4d7a3c..255787d 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -11,6 +11,14 @@ from stacosys.interface import app logger = logging.getLogger(__name__) +app.add_url_rule("/web", endpoint="index") +app.add_url_rule("/web/", endpoint="index") + + +@app.endpoint("index") +def index(): + return redirect('/web/admin') + def is_login_ok(username, password): hashed = hashlib.sha256(password.encode()).hexdigest().upper() @@ -35,7 +43,7 @@ def login(): @app.route('/web/logout', methods=["GET"]) def logout(): session.pop('user') - return redirect('/web/login') + return redirect('/web/admin') @app.route("/web/admin", methods=["GET"]) @@ -52,4 +60,6 @@ def admin_homepage(): @app.route("/web/admin", methods=["POST"]) def admin_action(): flash(request.form.get("comment") + " " + request.form.get("action")) + # rebuild RSS + #rss.generate() return redirect('/web/admin') From 7f2ff74ebe8997de0d3042cf3e0b0bac82210f17 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 13 Feb 2022 19:01:51 +0100 Subject: [PATCH 417/586] remove IMAP part --- run.py | 9 -- stacosys/conf/config.py | 7 - stacosys/core/cron.py | 88 +---------- stacosys/core/imap.py | 161 -------------------- stacosys/core/mailer.py | 39 ----- stacosys/core/rss.py | 35 ++--- stacosys/core/templater.py | 23 --- stacosys/interface/scheduler.py | 23 +-- stacosys/templates/en/approve_comment.tpl | 9 -- stacosys/templates/en/drop_comment.tpl | 9 -- stacosys/templates/en/new_comment.tpl | 16 -- stacosys/templates/en/notify_message.tpl | 1 - stacosys/templates/en/rss_title_message.tpl | 1 - stacosys/templates/fr/approve_comment.tpl | 9 -- stacosys/templates/fr/drop_comment.tpl | 9 -- stacosys/templates/fr/new_comment.tpl | 16 -- stacosys/templates/fr/notify_message.tpl | 1 - stacosys/templates/fr/rss_title_message.tpl | 1 - tests/test_config.py | 17 +-- tests/test_imap.py | 33 ---- tests/test_templater.py | 52 ------- 21 files changed, 24 insertions(+), 535 deletions(-) delete mode 100755 stacosys/core/imap.py delete mode 100644 stacosys/core/templater.py delete mode 100644 stacosys/templates/en/approve_comment.tpl delete mode 100644 stacosys/templates/en/drop_comment.tpl delete mode 100644 stacosys/templates/en/new_comment.tpl delete mode 100644 stacosys/templates/en/notify_message.tpl delete mode 100644 stacosys/templates/en/rss_title_message.tpl delete mode 100644 stacosys/templates/fr/approve_comment.tpl delete mode 100644 stacosys/templates/fr/drop_comment.tpl delete mode 100644 stacosys/templates/fr/new_comment.tpl delete mode 100644 stacosys/templates/fr/notify_message.tpl delete mode 100644 stacosys/templates/fr/rss_title_message.tpl delete mode 100644 tests/test_imap.py delete mode 100644 tests/test_templater.py diff --git a/run.py b/run.py index df5517d..4f8095f 100644 --- a/run.py +++ b/run.py @@ -72,11 +72,6 @@ def stacosys_server(config_pathname): # configure mailer mailer = Mailer( - conf.get(ConfigParameter.IMAP_HOST), - conf.get_int(ConfigParameter.IMAP_PORT), - conf.get_bool(ConfigParameter.IMAP_SSL), - conf.get(ConfigParameter.IMAP_LOGIN), - conf.get(ConfigParameter.IMAP_PASSWORD), conf.get(ConfigParameter.SMTP_HOST), conf.get_int(ConfigParameter.SMTP_PORT), conf.get_bool(ConfigParameter.SMTP_STARTTLS), @@ -94,14 +89,10 @@ def stacosys_server(config_pathname): # configure scheduler conf.put(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME).encode('utf-8')).hexdigest()) scheduler.configure( - conf.get_int(ConfigParameter.IMAP_POLLING), conf.get_int(ConfigParameter.COMMENT_POLLING), - conf.get(ConfigParameter.LANG), conf.get(ConfigParameter.SITE_NAME), - conf.get(ConfigParameter.SITE_TOKEN), conf.get(ConfigParameter.SITE_ADMIN_EMAIL), mailer, - rss, ) # inject config parameters into flask diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index cb68c0b..bbfe15e 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -17,13 +17,6 @@ class ConfigParameter(Enum): RSS_PROTO = "rss.proto" RSS_FILE = "rss.file" - IMAP_POLLING = "imap.polling" - IMAP_SSL = "imap.ssl" - IMAP_HOST = "imap.host" - IMAP_PORT = "imap.port" - IMAP_LOGIN = "imap.login" - IMAP_PASSWORD = "imap.password" - SMTP_STARTTLS = "smtp.starttls" SMTP_SSL = "smtp.ssl" SMTP_HOST = "smtp.host" diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index beed2ad..4be8b35 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -2,92 +2,13 @@ # -*- coding: utf-8 -*- import logging -import os -import re -from stacosys.core.mailer import Mailer -from stacosys.core.rss import Rss -from stacosys.core.templater import Templater, Template from stacosys.db import dao -from stacosys.model.email import Email - -REGEX_EMAIL_SUBJECT = r".*STACOSYS.*\[(\d+)\:(\w+)\]" logger = logging.getLogger(__name__) -current_path = os.path.dirname(__file__) -template_path = os.path.abspath(os.path.join(current_path, "../templates")) -templater = Templater(template_path) - -def fetch_mail_answers(lang, mailer: Mailer, rss: Rss, site_token): - while True: - msgs = mailer.fetch() - if len(msgs) == 0: - break - msg = msgs[0] - _process_answer_msg(msg, lang, mailer, rss, site_token) - mailer.delete(msg.id) - - -def _process_answer_msg(msg, lang, mailer: Mailer, rss: Rss, site_token): - # filter stacosys e-mails - m = re.search(REGEX_EMAIL_SUBJECT, msg.subject, re.DOTALL) - if not m: - return - - comment_id = int(m.group(1)) - submitted_token = m.group(2) - - # validate token - if submitted_token != site_token: - logger.warning("ignore corrupted email. Unknown token %d" % comment_id) - return - - if not msg.plain_text_content: - logger.warning("ignore empty email") - return - - _reply_comment_email(lang, mailer, rss, msg, comment_id) - - -def _reply_comment_email(lang, mailer: Mailer, rss: Rss, email: Email, comment_id): - # retrieve comment - comment = dao.find_comment_by_id(comment_id) - if not comment: - logger.warning("unknown comment %d" % comment_id) - return - - if comment.published: - logger.warning("ignore already published email. token %d" % comment_id) - return - - # safe logic: no answer or unknown answer is a go for publishing - if email.plain_text_content[:2].upper() == "NO": - logger.info("discard comment: %d" % comment_id) - dao.delete_comment(comment) - new_email_body = templater.get_template(lang, Template.DROP_COMMENT).render( - original=email.plain_text_content - ) - if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): - logger.warning("minor failure. cannot send rejection mail " + email.subject) - else: - # save publishing datetime - dao.publish_comment(comment) - logger.info("commit comment: %d" % comment_id) - - # rebuild RSS - rss.generate() - - # send approval confirmation email to admin - new_email_body = templater.get_template(lang, Template.APPROVE_COMMENT).render( - original=email.plain_text_content - ) - if not mailer.send(email.from_addr, "Re: " + email.subject, new_email_body): - logger.warning("minor failure. cannot send approval email " + email.subject) - - -def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): +def submit_new_comment(site_name, site_admin_email, mailer): for comment in dao.find_not_notified_comments(): comment_list = ( "author: %s" % comment.author_name, @@ -98,13 +19,10 @@ def submit_new_comment(lang, site_name, site_token, site_admin_email, mailer): "%s" % comment.content, "", ) - comment_text = "\n".join(comment_list) - email_body = templater.get_template(lang, Template.NEW_COMMENT).render( - url=comment.url, comment=comment_text - ) + email_body = "\n".join(comment_list) # send email to notify admin - subject = "STACOSYS %s: [%d:%s]" % (site_name, comment.id, site_token) + subject = "STACOSYS %s" % site_name if mailer.send(site_admin_email, subject, email_body): logger.debug("new comment processed ") diff --git a/stacosys/core/imap.py b/stacosys/core/imap.py deleted file mode 100755 index b53da1d..0000000 --- a/stacosys/core/imap.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -import base64 -import datetime -import email -import imaplib -import logging -import re -from email.message import Message - -from stacosys.model.email import Attachment, Email, Part - -filename_re = re.compile('filename="(.+)"|filename=([^;\n\r"\']+)', re.I | re.S) - - -class Mailbox(object): - def __init__(self, host, port, ssl, login, password): - self.logger = logging.getLogger(__name__) - self.host = host - self.port = port - self.ssl = ssl - self.login = login - self.password = password - - def __enter__(self): - if self.ssl: - self.imap = imaplib.IMAP4_SSL(self.host, self.port) - else: - self.imap = imaplib.IMAP4(self.host, self.port) - self.imap.login(self.login, self.password) - return self - - def __exit__(self, _type, value, traceback): - self.imap.close() - self.imap.logout() - - def get_count(self): - self.imap.select("Inbox") - _, data = self.imap.search(None, "ALL") - return sum(1 for _ in data[0].split()) - - def fetch_raw_message(self, num): - self.imap.select("Inbox") - _, data = self.imap.fetch(str(num), "(RFC822)") - email_msg = email.message_from_bytes(data[0][1]) - return email_msg - - def fetch_message(self, num): - raw_msg = self.fetch_raw_message(num) - - parts = [] - attachments = [] - plain_text_content = "no plain-text part" - for part in raw_msg.walk(): - if part.is_multipart(): - continue - - if _is_part_attachment(part): - attachments.append(_get_attachment(part)) - else: - try: - content = _to_plain_text_content(part) - parts.append( - Part(content=content, content_type=part.get_content_type()) - ) - if part.get_content_type() == "text/plain": - plain_text_content = content - except Exception: - logging.exception("cannot extract content from mail part") - - return Email( - id=num, - encoding="UTF-8", - date=_parse_date(raw_msg["Date"]).strftime("%Y-%m-%d %H:%M:%S"), - from_addr=raw_msg["From"], - to_addr=raw_msg["To"], - subject=_email_non_ascii_to_uft8(raw_msg["Subject"]), - parts=parts, - attachments=attachments, - plain_text_content=plain_text_content, - ) - - def delete_message(self, num): - self.imap.select("Inbox") - self.imap.store(str(num), "+FLAGS", r"\Deleted") - self.imap.expunge() - - def delete_all(self): - self.imap.select("Inbox") - _, data = self.imap.search(None, "ALL") - for num in data[0].split(): - self.imap.store(num, "+FLAGS", r"\Deleted") - self.imap.expunge() - - def print_msgs(self): - self.imap.select("Inbox") - _, data = self.imap.search(None, "ALL") - for num in reversed(data[0].split()): - status, data = self.imap.fetch(num, "(RFC822)") - self.logger.debug("Message %s\n%s\n" % (num, data[0][1])) - - -def _parse_date(v): - if v is None: - return datetime.datetime.now() - tt = email.utils.parsedate_tz(v) - if tt is None: - return datetime.datetime.now() - timestamp = email.utils.mktime_tz(tt) - date = datetime.datetime.fromtimestamp(timestamp) - return date - - -def _to_utf8(string, charset): - return string.decode(charset).encode("UTF-8").decode("UTF-8") - - -def _email_non_ascii_to_uft8(string): - # RFC 1342 is a recommendation that provides a way to represent non ASCII - # characters inside e-mail in a way that won’t confuse e-mail servers - subject = "" - for v, charset in email.header.decode_header(string): - if charset is None or charset == 'unknown-8bit': - if type(v) is bytes: - v = v.decode() - subject = subject + v - else: - subject = subject + _to_utf8(v, charset) - return subject - - -def _to_plain_text_content(part: Message) -> str: - content = part.get_payload(decode=True) - charset = part.get_param("charset", None) - if charset: - content = _to_utf8(content, charset) - elif type(content) == bytes: - content = content.decode("utf8") - # RFC 3676: remove automatic word-wrapping - return content.replace(" \r\n", " ") - - -def _is_part_attachment(part): - return part.get("Content-Disposition", None) - - -def _get_attachment(part) -> Attachment: - content_disposition = part.get("Content-Disposition", None) - r = filename_re.findall(content_disposition) - if r: - filename = sorted(r[0])[1] - else: - filename = "undefined" - content = base64.b64encode(part.get_payload(decode=True)) - content = content.decode() - return Attachment( - filename=_email_non_ascii_to_uft8(filename), - content=content, - content_type=part.get_content_type(), - ) diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index c8a2ffd..5d8fa11 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -8,19 +8,12 @@ from email.mime.text import MIMEText from email.message import EmailMessage from logging.handlers import SMTPHandler -from stacosys.core import imap - logger = logging.getLogger(__name__) class Mailer: def __init__( self, - imap_host, - imap_port, - imap_ssl, - imap_login, - imap_password, smtp_host, smtp_port, smtp_starttls, @@ -29,11 +22,6 @@ class Mailer: smtp_password, site_admin_email, ): - self._imap_host = imap_host - self._imap_port = imap_port - self._imap_ssl = imap_ssl - self._imap_login = imap_login - self._imap_password = imap_password self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_starttls = smtp_starttls @@ -42,26 +30,6 @@ class Mailer: self._smtp_password = smtp_password self._site_admin_email = site_admin_email - def _open_mailbox(self): - return imap.Mailbox( - self._imap_host, - self._imap_port, - self._imap_ssl, - self._imap_login, - self._imap_password, - ) - - def fetch(self): - msgs = [] - try: - with self._open_mailbox() as mbox: - count = mbox.get_count() - for num in range(count): - msgs.append(mbox.fetch_message(num + 1)) - except Exception: - logger.exception("fetch mail exception") - return msgs - def send(self, to_email, subject, message): # Create the container (outer) email message. @@ -87,13 +55,6 @@ class Mailer: success = False return success - def delete(self, id): - try: - with self._open_mailbox() as mbox: - mbox.delete_message(id) - except Exception: - logger.exception("delete mail exception") - def get_error_handler(self): if self._smtp_ssl: mail_handler = SSLSMTPHandler( diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 79366af..c3e3e9a 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -1,52 +1,44 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import os from datetime import datetime -import markdown import PyRSS2Gen +import markdown -from stacosys.core.templater import Templater, Template from stacosys.model.comment import Comment class Rss: def __init__( - self, - lang, - rss_file, - rss_proto, - site_name, - site_url, + self, + lang, + rss_file, + rss_proto, + site_name, + site_url, ): self._lang = lang self._rss_file = rss_file self._rss_proto = rss_proto self._site_name = site_name self._site_url = site_url - current_path = os.path.dirname(__file__) - template_path = os.path.abspath(os.path.join(current_path, "../templates")) - self._templater = Templater(template_path) def generate(self): - rss_title = self._templater.get_template( - self._lang, Template.RSS_TITLE_MESSAGE - ).render(site=self._site_name) md = markdown.Markdown() items = [] for row in ( - Comment.select() - .where(Comment.published) - .order_by(-Comment.published) - .limit(10) + Comment.select() + .where(Comment.published) + .order_by(-Comment.published) + .limit(10) ): item_link = "%s://%s%s" % (self._rss_proto, self._site_url, row.url) items.append( PyRSS2Gen.RSSItem( title="%s - %s://%s%s" - % (self._rss_proto, row.author_name, self._site_url, row.url), + % (self._rss_proto, row.author_name, self._site_url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), @@ -54,10 +46,11 @@ class Rss: ) ) + rss_title = 'Commentaires du site "%s"' % self._site_name rss = PyRSS2Gen.RSS2( title=rss_title, link="%s://%s" % (self._rss_proto, self._site_url), - description='Commentaires du site "%s"' % self._site_name, + description=rss_title, lastBuildDate=datetime.now(), items=items, ) diff --git a/stacosys/core/templater.py b/stacosys/core/templater.py deleted file mode 100644 index 9b00964..0000000 --- a/stacosys/core/templater.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from enum import Enum -from jinja2 import Environment, FileSystemLoader - - -class Template(Enum): - DROP_COMMENT = "drop_comment" - APPROVE_COMMENT = "approve_comment" - NEW_COMMENT = "new_comment" - NOTIFY_MESSAGE = "notify_message" - RSS_TITLE_MESSAGE = "rss_title_message" - WEB_COMMENT_APPROVAL = "web_comment_approval" - - -class Templater: - def __init__(self, template_path): - self._env = Environment(loader=FileSystemLoader(template_path)) - - def get_template(self, lang, template: Template): - return self._env.get_template(lang + "/" + template.value + ".tpl") - diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index 33ea3ef..e6615e4 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -9,31 +9,20 @@ class JobConfig(object): JOBS: list = [] - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 4}} + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 1}} def __init__( self, - imap_polling_seconds, new_comment_polling_seconds, - lang, site_name, - site_token, site_admin_email, mailer, - rss, ): self.JOBS = [ - { - "id": "fetch_mail", - "func": "stacosys.core.cron:fetch_mail_answers", - "args": [lang, mailer, rss, site_token], - "trigger": "interval", - "seconds": imap_polling_seconds, - }, { "id": "submit_new_comment", "func": "stacosys.core.cron:submit_new_comment", - "args": [lang, site_name, site_token, site_admin_email, mailer], + "args": [site_name, site_admin_email, mailer], "trigger": "interval", "seconds": new_comment_polling_seconds, }, @@ -41,25 +30,17 @@ class JobConfig(object): def configure( - imap_polling, comment_polling, - lang, site_name, - site_token, site_admin_email, mailer, - rss, ): app.config.from_object( JobConfig( - imap_polling, comment_polling, - lang, site_name, - site_token, site_admin_email, mailer, - rss, ) ) scheduler = APScheduler() diff --git a/stacosys/templates/en/approve_comment.tpl b/stacosys/templates/en/approve_comment.tpl deleted file mode 100644 index 145ca2c..0000000 --- a/stacosys/templates/en/approve_comment.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Hi, - -The comment should be published soon. It has been approved. - --- -Stacosys - - -{{ original }} diff --git a/stacosys/templates/en/drop_comment.tpl b/stacosys/templates/en/drop_comment.tpl deleted file mode 100644 index 6aaed72..0000000 --- a/stacosys/templates/en/drop_comment.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Hi, - -The comment will not be published. It has been dropped. - --- -Stacosys - - -{{ original }} diff --git a/stacosys/templates/en/new_comment.tpl b/stacosys/templates/en/new_comment.tpl deleted file mode 100644 index 490f714..0000000 --- a/stacosys/templates/en/new_comment.tpl +++ /dev/null @@ -1,16 +0,0 @@ -Hi, - -A new comment has been submitted for post {{ url }} - -You have two choices: -- reject the comment by replying NO (or no), -- accept the comment by sending back the email as it is. - -If you choose the latter option, Stacosys is going to publish the commennt. - -Please find comment details below: - -{{ comment }} - --- -Stacosys diff --git a/stacosys/templates/en/notify_message.tpl b/stacosys/templates/en/notify_message.tpl deleted file mode 100644 index 94a261f..0000000 --- a/stacosys/templates/en/notify_message.tpl +++ /dev/null @@ -1 +0,0 @@ -New comment diff --git a/stacosys/templates/en/rss_title_message.tpl b/stacosys/templates/en/rss_title_message.tpl deleted file mode 100644 index b0b1e30..0000000 --- a/stacosys/templates/en/rss_title_message.tpl +++ /dev/null @@ -1 +0,0 @@ -{{ site }} : comments diff --git a/stacosys/templates/fr/approve_comment.tpl b/stacosys/templates/fr/approve_comment.tpl deleted file mode 100644 index 35668d4..0000000 --- a/stacosys/templates/fr/approve_comment.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour, - -Le commentaire sera bientôt publié. Il a été approuvé. - --- -Stacosys - - -{{ original }} diff --git a/stacosys/templates/fr/drop_comment.tpl b/stacosys/templates/fr/drop_comment.tpl deleted file mode 100644 index 70e13ed..0000000 --- a/stacosys/templates/fr/drop_comment.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour, - -Le commentaire ne sera pas publié. Il a été rejeté. - --- -Stacosys - - -{{ original }} diff --git a/stacosys/templates/fr/new_comment.tpl b/stacosys/templates/fr/new_comment.tpl deleted file mode 100644 index 5671563..0000000 --- a/stacosys/templates/fr/new_comment.tpl +++ /dev/null @@ -1,16 +0,0 @@ -Bonjour, - -Un nouveau commentaire a été posté pour l'article {{ url }} - -Vous avez deux réponses possibles : -- rejeter le commentaire en répondant NO (ou no), -- accepter le commentaire en renvoyant cet email tel quel. - -Si cette dernière option est choisie, Stacosys publiera le commentaire très bientôt. - -Voici les détails concernant le commentaire : - -{{ comment }} - --- -Stacosys diff --git a/stacosys/templates/fr/notify_message.tpl b/stacosys/templates/fr/notify_message.tpl deleted file mode 100644 index 5455f77..0000000 --- a/stacosys/templates/fr/notify_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Nouveau commentaire diff --git a/stacosys/templates/fr/rss_title_message.tpl b/stacosys/templates/fr/rss_title_message.tpl deleted file mode 100644 index db993f6..0000000 --- a/stacosys/templates/fr/rss_title_message.tpl +++ /dev/null @@ -1 +0,0 @@ -{{ site }} : commentaires diff --git a/tests/test_config.py b/tests/test_config.py index 3bee34b..8fc4765 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,8 +7,7 @@ from stacosys.conf.config import Config, ConfigParameter EXPECTED_DB_SQLITE_FILE = "db.sqlite" EXPECTED_HTTP_PORT = 8080 -EXPECTED_IMAP_PORT = "5000" -EXPECTED_IMAP_LOGIN = "user" +EXPECTED_LANG = "fr" class ConfigTestCase(unittest.TestCase): @@ -17,24 +16,18 @@ class ConfigTestCase(unittest.TestCase): self.conf = Config() self.conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) self.conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) - self.conf.put(ConfigParameter.IMAP_PORT, EXPECTED_IMAP_PORT) self.conf.put(ConfigParameter.SMTP_STARTTLS, "yes") - self.conf.put(ConfigParameter.IMAP_SSL, "false") def test_exists(self): self.assertTrue(self.conf.exists(ConfigParameter.DB_SQLITE_FILE)) - self.assertFalse(self.conf.exists(ConfigParameter.IMAP_HOST)) def test_get(self): self.assertEqual(self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE) self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) - self.assertEqual(self.conf.get(ConfigParameter.IMAP_PORT), EXPECTED_IMAP_PORT) - self.assertEqual(self.conf.get_int(ConfigParameter.IMAP_PORT), int(EXPECTED_IMAP_PORT)) self.assertEqual(self.conf.get_int(ConfigParameter.HTTP_PORT), 8080) self.assertTrue(self.conf.get_bool(ConfigParameter.SMTP_STARTTLS)) - self.assertFalse(self.conf.get_bool(ConfigParameter.IMAP_SSL)) try: self.conf.get_bool(ConfigParameter.DB_SQLITE_FILE) self.assertTrue(False) @@ -42,7 +35,7 @@ class ConfigTestCase(unittest.TestCase): pass def test_put(self): - self.assertFalse(self.conf.exists(ConfigParameter.IMAP_LOGIN)) - self.conf.put(ConfigParameter.IMAP_LOGIN, EXPECTED_IMAP_LOGIN) - self.assertTrue(self.conf.exists(ConfigParameter.IMAP_LOGIN)) - self.assertEqual(self.conf.get(ConfigParameter.IMAP_LOGIN), EXPECTED_IMAP_LOGIN) + self.assertFalse(self.conf.exists(ConfigParameter.LANG)) + self.conf.put(ConfigParameter.LANG, EXPECTED_LANG) + self.assertTrue(self.conf.exists(ConfigParameter.LANG)) + self.assertEqual(self.conf.get(ConfigParameter.LANG), EXPECTED_LANG) diff --git a/tests/test_imap.py b/tests/test_imap.py deleted file mode 100644 index 474fe85..0000000 --- a/tests/test_imap.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import datetime -import unittest -from email.header import Header -from email.message import Message - -from stacosys.core import imap - - -class ImapTestCase(unittest.TestCase): - - def test_utf8_decode(self): - h = Header(s="Chez Darty vous avez re\udcc3\udca7u un nouvel aspirateur Vacuum gratuit jl8nz", - charset="unknown-8bit") - decoded = imap._email_non_ascii_to_uft8(h) - self.assertEqual(decoded, "Chez Darty vous avez reçu un nouvel aspirateur Vacuum gratuit jl8nz") - - def test_parse_date(self): - now = datetime.datetime.now() - self.assertGreaterEqual(imap._parse_date(None), now) - parsed = imap._parse_date("Wed, 8 Dec 2021 20:05:20 +0100") - self.assertEqual(parsed.day, 8) - self.assertEqual(parsed.month, 12) - self.assertEqual(parsed.year, 2021) - # do not compare hours. don't care about timezone - - def test_to_plain_text_content(self): - msg = Message() - payload = b"non\r\n\r\nLe 08/12/2021 \xc3\xa0 20:04, kianby@free.fr a \xc3\xa9crit\xc2\xa0:\r\n> Bonjour,\r\n>\r\n> Un nouveau commentaire a \xc3\xa9t\xc3\xa9 post\xc3\xa9 pour l'article /2021/rester-discret-sur-github//\r\n>\r\n> Vous avez deux r\xc3\xa9ponses possibles :\r\n> - rejeter le commentaire en r\xc3\xa9pondant NO (ou no),\r\n> - accepter le commentaire en renvoyant cet email tel quel.\r\n>\r\n> Si cette derni\xc3\xa8re option est choisie, Stacosys publiera le commentaire tr\xc3\xa8s bient\xc3\xb4t.\r\n>\r\n> Voici les d\xc3\xa9tails concernant le commentaire :\r\n>\r\n> author: ET Rate\r\n> site:\r\n> date: 2021-12-08 20:03:58\r\n> url: /2021/rester-discret-sur-github//\r\n>\r\n> gfdgdgf\r\n>\r\n>\r\n> --\r\n> Stacosys\r\n" - msg.set_payload(payload, "UTF-8") - self.assertTrue(imap._to_plain_text_content(msg)) diff --git a/tests/test_templater.py b/tests/test_templater.py deleted file mode 100644 index 117d19d..0000000 --- a/tests/test_templater.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import os -import unittest - -from stacosys.core.templater import Templater, Template - - -class TemplateTestCase(unittest.TestCase): - - def get_template_content(self, lang, template_name, **kwargs): - current_path = os.path.dirname(__file__) - template_path = os.path.abspath(os.path.join(current_path, "../stacosys/templates")) - template = Templater(template_path).get_template(lang, template_name) - return template.render(kwargs) - - def test_approve_comment(self): - content = self.get_template_content("fr", Template.APPROVE_COMMENT, original="[texte]") - self.assertTrue(content.startswith("Bonjour,\n\nLe commentaire sera bientôt publié.")) - self.assertTrue(content.endswith("[texte]")) - content = self.get_template_content("en", Template.APPROVE_COMMENT, original="[texte]") - self.assertTrue(content.startswith("Hi,\n\nThe comment should be published soon.")) - self.assertTrue(content.endswith("[texte]")) - - def test_drop_comment(self): - content = self.get_template_content("fr", Template.DROP_COMMENT, original="[texte]") - self.assertTrue(content.startswith("Bonjour,\n\nLe commentaire ne sera pas publié.")) - self.assertTrue(content.endswith("[texte]")) - content = self.get_template_content("en", Template.DROP_COMMENT, original="[texte]") - self.assertTrue(content.startswith("Hi,\n\nThe comment will not be published.")) - self.assertTrue(content.endswith("[texte]")) - - def test_new_comment(self): - content = self.get_template_content("fr", Template.NEW_COMMENT, comment="[comment]") - self.assertTrue(content.startswith("Bonjour,\n\nUn nouveau commentaire a été posté")) - self.assertTrue(content.endswith("[comment]\n\n--\nStacosys")) - content = self.get_template_content("en", Template.NEW_COMMENT, comment="[comment]") - self.assertTrue(content.startswith("Hi,\n\nA new comment has been submitted")) - self.assertTrue(content.endswith("[comment]\n\n--\nStacosys")) - - def test_notify_message(self): - content = self.get_template_content("fr", Template.NOTIFY_MESSAGE) - self.assertEqual("Nouveau commentaire", content) - content = self.get_template_content("en", Template.NOTIFY_MESSAGE) - self.assertEqual("New comment", content) - - def test_rss_title(self): - content = self.get_template_content("fr", Template.RSS_TITLE_MESSAGE, site="[site]") - self.assertEqual("[site] : commentaires", content) - content = self.get_template_content("en", Template.RSS_TITLE_MESSAGE, site="[site]") - self.assertEqual("[site] : comments", content) From 185641e6d0ec4fb5930b5215563cdbbb1e734b44 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:51:50 +0100 Subject: [PATCH 418/586] simplify mailer --- config.ini | 14 +---- run.py | 8 --- stacosys/conf/config.py | 2 - stacosys/core/cron.py | 4 +- stacosys/core/mailer.py | 107 ++++++-------------------------- stacosys/interface/scheduler.py | 5 +- tests/test_config.py | 2 - 7 files changed, 24 insertions(+), 118 deletions(-) diff --git a/config.ini b/config.ini index fda705f..5698572 100755 --- a/config.ini +++ b/config.ini @@ -19,19 +19,9 @@ port = 8100 proto = https file = comments.xml -[imap] -polling = 120 -host = mail.gandi.net -ssl = false -port = 993 -login = blog@mydomain.com -password = MYPASSWORD - [smtp] -host = mail.gandi.net -starttls = true -ssl = false -port = 587 +host = smtp.mail.com +port = 465 login = blog@mydomain.com password = MYPASSWORD diff --git a/run.py b/run.py index 4f8095f..5a669f5 100644 --- a/run.py +++ b/run.py @@ -74,24 +74,16 @@ def stacosys_server(config_pathname): mailer = Mailer( conf.get(ConfigParameter.SMTP_HOST), conf.get_int(ConfigParameter.SMTP_PORT), - conf.get_bool(ConfigParameter.SMTP_STARTTLS), - conf.get_bool(ConfigParameter.SMTP_SSL), conf.get(ConfigParameter.SMTP_LOGIN), conf.get(ConfigParameter.SMTP_PASSWORD), conf.get(ConfigParameter.SITE_ADMIN_EMAIL) ) - # configure mailer logger - mail_handler = mailer.get_error_handler() - logger.addHandler(mail_handler) - app.logger.addHandler(mail_handler) - # configure scheduler conf.put(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME).encode('utf-8')).hexdigest()) scheduler.configure( conf.get_int(ConfigParameter.COMMENT_POLLING), conf.get(ConfigParameter.SITE_NAME), - conf.get(ConfigParameter.SITE_ADMIN_EMAIL), mailer, ) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index bbfe15e..9e923d4 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -17,8 +17,6 @@ class ConfigParameter(Enum): RSS_PROTO = "rss.proto" RSS_FILE = "rss.file" - SMTP_STARTTLS = "smtp.starttls" - SMTP_SSL = "smtp.ssl" SMTP_HOST = "smtp.host" SMTP_PORT = "smtp.port" SMTP_LOGIN = "smtp.login" diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py index 4be8b35..2dd87c0 100644 --- a/stacosys/core/cron.py +++ b/stacosys/core/cron.py @@ -8,7 +8,7 @@ from stacosys.db import dao logger = logging.getLogger(__name__) -def submit_new_comment(site_name, site_admin_email, mailer): +def submit_new_comment(site_name, mailer): for comment in dao.find_not_notified_comments(): comment_list = ( "author: %s" % comment.author_name, @@ -23,7 +23,7 @@ def submit_new_comment(site_name, site_admin_email, mailer): # send email to notify admin subject = "STACOSYS %s" % site_name - if mailer.send(site_admin_email, subject, email_body): + if mailer.send(subject, email_body): logger.debug("new comment processed ") # save notification datetime diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 5d8fa11..defa09a 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -3,109 +3,40 @@ import logging import smtplib -import email.utils +import ssl + from email.mime.text import MIMEText -from email.message import EmailMessage -from logging.handlers import SMTPHandler logger = logging.getLogger(__name__) class Mailer: def __init__( - self, - smtp_host, - smtp_port, - smtp_starttls, - smtp_ssl, - smtp_login, - smtp_password, - site_admin_email, + self, + smtp_host, + smtp_port, + smtp_login, + smtp_password, + site_admin_email, ): self._smtp_host = smtp_host self._smtp_port = smtp_port - self._smtp_starttls = smtp_starttls - self._smtp_ssl = smtp_ssl self._smtp_login = smtp_login self._smtp_password = smtp_password self._site_admin_email = site_admin_email - def send(self, to_email, subject, message): + def send(self, subject, message): + sender = self._smtp_login + receivers = [self._site_admin_email] - # Create the container (outer) email message. msg = MIMEText(message) msg["Subject"] = subject - msg["To"] = to_email - msg["From"] = self._smtp_login + msg["To"] = self._site_admin_email + msg["From"] = sender - success = True - try: - if self._smtp_ssl: - s = smtplib.SMTP_SSL(self._smtp_host, self._smtp_port) - else: - s = smtplib.SMTP(self._smtp_host, self._smtp_port) - if self._smtp_starttls: - s.starttls() - if self._smtp_login: - s.login(self._smtp_login, self._smtp_password) - s.send_message(msg) - s.quit() - except Exception: - logger.exception("send mail exception") - success = False - return success - - def get_error_handler(self): - if self._smtp_ssl: - mail_handler = SSLSMTPHandler( - mailhost=( - self._smtp_host, - self._smtp_port, - ), - credentials=( - self._smtp_login, - self._smtp_password, - ), - fromaddr=self._smtp_login, - toaddrs=self._site_admin_email, - subject="Stacosys error", - ) - else: - mail_handler = SMTPHandler( - mailhost=( - self._smtp_host, - self._smtp_port, - ), - credentials=( - self._smtp_login, - self._smtp_password, - ), - fromaddr=self._smtp_login, - toaddrs=self._site_admin_email, - subject="Stacosys error", - ) - mail_handler.setLevel(logging.ERROR) - return mail_handler - - -class SSLSMTPHandler(SMTPHandler): - def emit(self, record): - """ - Emit a record. - - Format the record and send it to the specified addressees. - """ - try: - smtp = smtplib.SMTP_SSL(self.mailhost, self.mailport) - msg = EmailMessage() - msg["From"] = self.fromaddr - msg["To"] = ",".join(self.toaddrs) - msg["Subject"] = self.getSubject(record) - msg["Date"] = email.utils.localtime() - msg.set_content(self.format(record)) - if self.username: - smtp.login(self.username, self.password) - smtp.send_message(msg) - smtp.quit() - except Exception: - self.handleError(record) + context = ssl.create_default_context() + with smtplib.SMTP_SSL(self._smtp_host, self._smtp_port, context=context) as server: + server.login(self._smtp_login, self._smtp_password) + server.send_message(msg, sender, receivers) + return True + return False diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py index e6615e4..1de9a0e 100644 --- a/stacosys/interface/scheduler.py +++ b/stacosys/interface/scheduler.py @@ -15,14 +15,13 @@ class JobConfig(object): self, new_comment_polling_seconds, site_name, - site_admin_email, mailer, ): self.JOBS = [ { "id": "submit_new_comment", "func": "stacosys.core.cron:submit_new_comment", - "args": [site_name, site_admin_email, mailer], + "args": [site_name, mailer], "trigger": "interval", "seconds": new_comment_polling_seconds, }, @@ -32,14 +31,12 @@ class JobConfig(object): def configure( comment_polling, site_name, - site_admin_email, mailer, ): app.config.from_object( JobConfig( comment_polling, site_name, - site_admin_email, mailer, ) ) diff --git a/tests/test_config.py b/tests/test_config.py index 8fc4765..e98a9e8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -16,7 +16,6 @@ class ConfigTestCase(unittest.TestCase): self.conf = Config() self.conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) self.conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) - self.conf.put(ConfigParameter.SMTP_STARTTLS, "yes") def test_exists(self): self.assertTrue(self.conf.exists(ConfigParameter.DB_SQLITE_FILE)) @@ -27,7 +26,6 @@ class ConfigTestCase(unittest.TestCase): self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) self.assertEqual(self.conf.get_int(ConfigParameter.HTTP_PORT), 8080) - self.assertTrue(self.conf.get_bool(ConfigParameter.SMTP_STARTTLS)) try: self.conf.get_bool(ConfigParameter.DB_SQLITE_FILE) self.assertTrue(False) From 5f2827470619c24525d6c7557705aaabe41895b0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:21:12 +0100 Subject: [PATCH 419/586] remove cron tasks --- config.ini | 1 - dbmigration/create_empty_db.py | 3 -- dbmigration/migrate_from_2.1_to_3.0.py | 38 ---------------------- poetry.lock | 26 +++++++-------- pyproject.toml | 2 +- run.py | 12 ++----- stacosys/conf/config.py | 2 -- stacosys/core/cron.py | 32 ------------------ stacosys/interface/form.py | 37 +++++++++++++++++++-- stacosys/interface/scheduler.py | 45 -------------------------- 10 files changed, 50 insertions(+), 148 deletions(-) delete mode 100644 dbmigration/migrate_from_2.1_to_3.0.py delete mode 100644 stacosys/core/cron.py delete mode 100644 stacosys/interface/scheduler.py diff --git a/config.ini b/config.ini index 5698572..17e4cd3 100755 --- a/config.ini +++ b/config.ini @@ -3,7 +3,6 @@ [main] lang = fr db_sqlite_file = db.sqlite -newcomment_polling = 60 [site] name = "My blog" diff --git a/dbmigration/create_empty_db.py b/dbmigration/create_empty_db.py index 8bb64ac..a597869 100644 --- a/dbmigration/create_empty_db.py +++ b/dbmigration/create_empty_db.py @@ -6,9 +6,6 @@ import sqlite3 connection = sqlite3.connect("db.sqlite") cursor = connection.cursor() -# What script performs: -# - first, remove site table: crash here if table doesn't exist (compatibility test without effort) -# - remove site_id colum from comment table script = """ CREATE TABLE comment ( id INTEGER NOT NULL PRIMARY KEY, diff --git a/dbmigration/migrate_from_2.1_to_3.0.py b/dbmigration/migrate_from_2.1_to_3.0.py deleted file mode 100644 index e421d6e..0000000 --- a/dbmigration/migrate_from_2.1_to_3.0.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -import sqlite3 -import datetime -from ulid import ULID - -# add column ulid -connection = sqlite3.connect("db.sqlite") -cursor = connection.cursor() -script = """ -PRAGMA foreign_keys = OFF; -BEGIN TRANSACTION; -ALTER TABLE comment ADD ulid INTEGER; -COMMIT; -PRAGMA foreign_keys = ON; -""" -cursor.executescript(script) -connection.close() - -# fill in ulid column -connection = sqlite3.connect("db.sqlite") -cursor = connection.cursor() -updates = [] -for row in cursor.execute('SELECT * FROM comment'): - row_id = row[0] - string_created = row[2] - date_created = datetime.datetime.strptime(string_created, "%Y-%m-%d %H:%M:%S") - ulid = ULID.from_datetime(date_created) - update = "UPDATE comment SET ulid = " + str(int(ulid)) + " WHERE id = " + str(row_id) - print(update) - updates.append(update) - -for update in updates: - pass - connection.execute(update) -connection.commit() -connection.close() diff --git a/poetry.lock b/poetry.lock index 70cb658..4073351 100644 --- a/poetry.lock +++ b/poetry.lock @@ -54,6 +54,14 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +[[package]] +name = "background" +version = "0.2.1" +description = "It does what it says it does." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "backports.zoneinfo" version = "0.2.1" @@ -495,14 +503,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" -[[package]] -name = "python-ulid" -version = "1.0.3" -description = "Universally Unique Lexicographically Sortable Identifier" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "pytz" version = "2021.3" @@ -696,7 +696,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "24b77862cfbece0c68447f4d026bed51431e3a655a92dde2697368e758bfac89" +content-hash = "1062100a70ba0ca6a9f5db6470d0a980784af1d9b96d0bb681e1b65bacc204cf" [metadata.files] appdirs = [ @@ -715,6 +715,10 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] +background = [ + {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, + {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, +] "backports.zoneinfo" = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, @@ -1008,10 +1012,6 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-ulid = [ - {file = "python-ulid-1.0.3.tar.gz", hash = "sha256:5dd8b969312a40e2212cec9c1ad63f25d4b6eafd92ee3195883e0287b6e9d19e"}, - {file = "python_ulid-1.0.3-py3-none-any.whl", hash = "sha256:8704dc20f547f531fe3a41d4369842d737a0f275403b909d0872e7ea0fe8d6f2"}, -] pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, diff --git a/pyproject.toml b/pyproject.toml index 718ebee..4dd72e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ requests = "^2.25.1" coverage = "^5.5" peewee = "^3.14.8" tox = "^3.24.5" -python-ulid = "^1.0.3" +background = "^0.2.1" [tool.poetry.dev-dependencies] rope = "^0.16.0" diff --git a/run.py b/run.py index 5a669f5..b295253 100644 --- a/run.py +++ b/run.py @@ -2,7 +2,6 @@ # -*- coding: UTF-8 -*- import argparse -import hashlib import logging import os import sys @@ -14,7 +13,6 @@ from stacosys.db import database from stacosys.interface import api from stacosys.interface import app from stacosys.interface import form -from stacosys.interface import scheduler from stacosys.interface.web import admin @@ -79,20 +77,14 @@ def stacosys_server(config_pathname): conf.get(ConfigParameter.SITE_ADMIN_EMAIL) ) - # configure scheduler - conf.put(ConfigParameter.SITE_TOKEN, hashlib.sha1(conf.get(ConfigParameter.SITE_NAME).encode('utf-8')).hexdigest()) - scheduler.configure( - conf.get_int(ConfigParameter.COMMENT_POLLING), - conf.get(ConfigParameter.SITE_NAME), - mailer, - ) - # inject config parameters into flask app.config.update(LANG=conf.get(ConfigParameter.LANG)) + app.config.update(SITE_NAME=conf.get(ConfigParameter.SITE_NAME)) app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) app.config.update(WEB_USERNAME=conf.get(ConfigParameter.WEB_USERNAME)) app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) + app.config.update(MAILER=mailer) logger.info(f"start interfaces {api} {form} {admin}") # start Flask diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 9e923d4..4e77fb1 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -9,7 +9,6 @@ import profig class ConfigParameter(Enum): DB_SQLITE_FILE = "main.db_sqlite_file" LANG = "main.lang" - COMMENT_POLLING = "main.newcomment_polling" HTTP_HOST = "http.host" HTTP_PORT = "http.port" @@ -24,7 +23,6 @@ class ConfigParameter(Enum): SITE_NAME = "site.name" SITE_URL = "site.url" - SITE_TOKEN = "site.token" SITE_ADMIN_EMAIL = "site.admin_email" SITE_REDIRECT = "site.redirect" diff --git a/stacosys/core/cron.py b/stacosys/core/cron.py deleted file mode 100644 index 2dd87c0..0000000 --- a/stacosys/core/cron.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging - -from stacosys.db import dao - -logger = logging.getLogger(__name__) - - -def submit_new_comment(site_name, mailer): - for comment in dao.find_not_notified_comments(): - comment_list = ( - "author: %s" % comment.author_name, - "site: %s" % comment.author_site, - "date: %s" % comment.created, - "url: %s" % comment.url, - "", - "%s" % comment.content, - "", - ) - email_body = "\n".join(comment_list) - - # send email to notify admin - subject = "STACOSYS %s" % site_name - if mailer.send(subject, email_body): - logger.debug("new comment processed ") - - # save notification datetime - dao.notify_comment(comment) - else: - logger.warning("rescheduled. send mail failure " + subject) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 085c586..8bdabc0 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - import logging +import background from flask import abort, redirect, request from stacosys.db import dao @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) @app.route("/newcomment", methods=["POST"]) def new_form_comment(): - data = request.form logger.info("form data " + str(data)) @@ -40,7 +39,10 @@ def new_form_comment(): abort(400) # add a row to Comment table - dao.create_comment(url, author_name, author_site, author_gravatar, message) + comment = dao.create_comment(url, author_name, author_site, author_gravatar, message) + + # send notification e-mail asynchronously + submit_new_comment(comment) return redirect(app.config.get("SITE_REDIRECT"), code=302) @@ -51,3 +53,32 @@ def check_form_data(d): return not filtered +@background.task +def submit_new_comment(comment): + comment_list = ( + "Web admin interface: %s/web/admin" % app.config.get("SITE_URL"), + "", + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.created, + "url: %s" % comment.url, + "", + "%s" % comment.content, + "", + ) + email_body = "\n".join(comment_list) + + # send email to notify admin + subject = "STACOSYS " + app.config.get("SITE_NAME") + if app.config.get("MAILER").send(subject, email_body): + logger.debug("new comment processed") + + # save notification datetime + dao.notify_comment(comment) + else: + logger.warning("rescheduled. send mail failure " + subject) + + +@background.callback +def submit_new_comment_callback(future): + pass diff --git a/stacosys/interface/scheduler.py b/stacosys/interface/scheduler.py deleted file mode 100644 index 1de9a0e..0000000 --- a/stacosys/interface/scheduler.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from flask_apscheduler import APScheduler -from stacosys.interface import app - - -class JobConfig(object): - - JOBS: list = [] - - SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 1}} - - def __init__( - self, - new_comment_polling_seconds, - site_name, - mailer, - ): - self.JOBS = [ - { - "id": "submit_new_comment", - "func": "stacosys.core.cron:submit_new_comment", - "args": [site_name, mailer], - "trigger": "interval", - "seconds": new_comment_polling_seconds, - }, - ] - - -def configure( - comment_polling, - site_name, - mailer, -): - app.config.from_object( - JobConfig( - comment_polling, - site_name, - mailer, - ) - ) - scheduler = APScheduler() - scheduler.init_app(app) - scheduler.start() From ed430799f5aac37d4052d5957b47369c56afb646 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:25:59 +0100 Subject: [PATCH 420/586] remove flash apscheduler package --- poetry.lock | 33 +-------------------------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4073351..8c87e49 100644 --- a/poetry.lock +++ b/poetry.lock @@ -232,19 +232,6 @@ Werkzeug = ">=2.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] -[[package]] -name = "flask-apscheduler" -version = "1.12.3" -description = "Adds APScheduler support to Flask" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -apscheduler = ">=3.2.0,<4.0.0" -flask = ">=0.10.1" -python-dateutil = ">=2.4.2" - [[package]] name = "idna" version = "3.3" @@ -492,17 +479,6 @@ toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - [[package]] name = "pytz" version = "2021.3" @@ -696,7 +672,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "1062100a70ba0ca6a9f5db6470d0a980784af1d9b96d0bb681e1b65bacc204cf" +content-hash = "1db9a9c6f78a4148493fa7ab87741373fd6eccd7c078657de7dd00d3af8482b9" [metadata.files] appdirs = [ @@ -837,9 +813,6 @@ flask = [ {file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"}, {file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"}, ] -flask-apscheduler = [ - {file = "Flask-APScheduler-1.12.3.tar.gz", hash = "sha256:d60948d1f2be9eb4772f68c3308ba3f973755219d13947266f89292ad6df63fc"}, -] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -1008,10 +981,6 @@ pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, diff --git a/pyproject.toml b/pyproject.toml index 4dd72e5..f01c90c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ apscheduler = "^3.6.3" pyrss2gen = "^1.1" profig = "^0.5.1" markdown = "^3.1.1" -flask_apscheduler = "^1.11.0" Flask = "^2.0.1" requests = "^2.25.1" coverage = "^5.5" From 04c2d8f685c5600a16d7073845fceffe42b5aa3b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:26:17 +0100 Subject: [PATCH 421/586] todo action handler --- run.py | 1 + stacosys/interface/web/admin.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/run.py b/run.py index b295253..b9b7e14 100644 --- a/run.py +++ b/run.py @@ -85,6 +85,7 @@ def stacosys_server(config_pathname): app.config.update(WEB_USERNAME=conf.get(ConfigParameter.WEB_USERNAME)) app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) app.config.update(MAILER=mailer) + app.config.update(RSS=rss) logger.info(f"start interfaces {api} {form} {admin}") # start Flask diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index 255787d..a282965 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -60,6 +60,7 @@ def admin_homepage(): @app.route("/web/admin", methods=["POST"]) def admin_action(): flash(request.form.get("comment") + " " + request.form.get("action")) + # TODO process action # rebuild RSS - #rss.generate() + app.config.get("RSS").generate() return redirect('/web/admin') From de07987ed12678d6e8ea3660505d96a7dabd9edf Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:44:11 +0100 Subject: [PATCH 422/586] Finalize version 3.0 --- README.md | 21 +-- poetry.lock | 187 +++++++++------------ pyproject.toml | 2 +- run.py | 3 +- stacosys/db/database.py | 16 +- stacosys/interface/templates/login_fr.html | 2 +- stacosys/interface/web/admin.py | 22 ++- tests/test_api.py | 3 +- tests/test_db.py | 3 +- tests/test_form.py | 3 +- 10 files changed, 118 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 321fe82..98b075e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,7 @@ ## Stacosys -Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an humble alternative to comment hosting services like Disqus. Stacosys protects your readers's privacy. - -Stacosys works with any static blog or even a simple HTML page. It uses e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy. E-mails are reliable and an universal way to communicate. You can answer from any device using an e-mail client. +Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide a basic alternative to comment hosting services like Disqus. Stacosys works with any static blog or even a simple HTML page. ### Features overview @@ -16,25 +14,24 @@ Stacosys main feature is comment management. Here is the workflow: - Readers submit comments via a comment form embedded in blog pages -- Blog administrator receives an email notification from Stacosys when a +- Blog administrator receives an e-mail notification from Stacosys when a comment is submitted -- Blog administrator can approve or drop the comment by replying to e-mail +- Blog administrator can approve or drop the comment through a simple web admin interface - Stacosys stores approved comment in its database. -Privacy concerns: only surname, gravatar id and comment itself are stored in DB. E-mail is requested in submission form (but optional) to resolve gravatar id and it it not sent to stacosys. +Privacy concerns: only surname, gravatar id and comment itself are stored in DB. E-mail is optionally requested in submission form to resolve gravatar id but never sent to Stacosys. -Stacosys is localized (english and french). +Stacosys is more or less localized (english and french). ### Technically speaking, how does it work? -Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a form request allows to post a comment which is relayed to the administrator by e-mail. For this purpose a dedicated email is assigned to Stacosys. - +Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a request allows retrieving comments for a given page. Similarly, a form request allows to post a comment which is relayed to the administrator by e-mail. For this purpose an SMTP configuration is needed. ### Little FAQ *How do you block spammers?* -- Current comment form is basic: no captcha support but protected by an honey pot. +- Current comment form is basic: no captcha support but protected by a honeypot. *Which database is used?* @@ -49,8 +46,8 @@ Stacosys can be hosted on the same server or on a different server than the blog ### Installation -Build is based on [Poetry](https://python-poetry.org/) but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/r/kianby/stacosys). +Build and Dependency management relies on [Poetry](https://python-poetry.org/), but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/r/kianby/stacosys). ### Improvements -Stacosys fits my needs and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork the project and enhance the project if you need more features. +Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. diff --git a/poetry.lock b/poetry.lock index 8c87e49..a778f91 100644 --- a/poetry.lock +++ b/poetry.lock @@ -105,7 +105,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.10" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -116,7 +116,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "main" optional = false @@ -178,7 +178,7 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.4.2" +version = "3.6.0" description = "A platform independent file lock." category = "main" optional = false @@ -203,7 +203,7 @@ pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "flake8-black" -version = "0.2.3" +version = "0.2.4" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false @@ -216,7 +216,7 @@ toml = "*" [[package]] name = "flask" -version = "2.0.2" +version = "2.0.3" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -242,7 +242,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -254,7 +254,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -266,11 +266,11 @@ python-versions = "*" [[package]] name = "itsdangerous" -version = "2.0.1" +version = "2.1.0" description = "Safely pass data to untrusted environments and back." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "jinja2" @@ -302,11 +302,11 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.0" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mccabe" @@ -361,7 +361,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "peewee" -version = "3.14.8" +version = "3.14.9" description = "a little orm" category = "main" optional = false @@ -369,7 +369,7 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false @@ -584,7 +584,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "4.0.1" +version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "dev" optional = false @@ -630,7 +630,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.13.0" +version = "20.13.1" description = "Virtual Python Environment builder" category = "main" optional = false @@ -648,7 +648,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [[package]] name = "werkzeug" -version = "2.0.2" +version = "2.0.3" description = "The comprehensive WSGI web application library." category = "main" optional = false @@ -721,12 +721,12 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -798,36 +798,36 @@ docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] filelock = [ - {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, - {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] flake8-black = [ - {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, - {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, + {file = "flake8-black-0.2.4.tar.gz", hash = "sha256:a7871bfd1cbff431a1fc91ba60ae154510c80f575e6b9a2bbb13dfb4650afd22"}, + {file = "flake8_black-0.2.4-py3-none-any.whl", hash = "sha256:0a70dfd97c8439827f365dc6dbc6c8c9cc087f0833625c6cc6848ff7876256be"}, ] flask = [ - {file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"}, - {file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"}, + {file = "Flask-2.0.3-py3-none-any.whl", hash = "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f"}, + {file = "Flask-2.0.3.tar.gz", hash = "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"}, + {file = "importlib_metadata-4.11.1.tar.gz", hash = "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] itsdangerous = [ - {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, - {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, + {file = "itsdangerous-2.1.0-py3-none-any.whl", hash = "sha256:29285842166554469a56d427addc0843914172343784cb909695fdbe90a3e129"}, + {file = "itsdangerous-2.1.0.tar.gz", hash = "sha256:d848fcb8bc7d507c4546b448574e8a44fc4ea2ba84ebf8d783290d53e81992f5"}, ] jinja2 = [ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, @@ -838,75 +838,46 @@ markdown = [ {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"}, + {file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -941,11 +912,11 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] peewee = [ - {file = "peewee-3.14.8.tar.gz", hash = "sha256:01bd7f734defb08d7a3346a0c0ca7011bc8d0d685934ec0e001b3371d522ec53"}, + {file = "peewee-3.14.9.tar.gz", hash = "sha256:69c1b88dc89b184231cc1ce6df241075aca5cec43e89749cc4a63108f9ceea47"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, + {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -1119,8 +1090,8 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] tzdata = [ {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, @@ -1135,12 +1106,12 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ - {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, - {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, + {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, + {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, ] werkzeug = [ - {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, - {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, + {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"}, + {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"}, ] zipp = [ {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, diff --git a/pyproject.toml b/pyproject.toml index f01c90c..d7bf198 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "2.1" +version = "3.0" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" diff --git a/run.py b/run.py index b9b7e14..948f8b6 100644 --- a/run.py +++ b/run.py @@ -53,8 +53,7 @@ def stacosys_server(config_pathname): sys.exit(1) # initialize database - db = database.Database() - db.setup(db_pathname) + database.setup(db_pathname) logger.info("Start Stacosys application") diff --git a/stacosys/db/database.py b/stacosys/db/database.py index 5b3af96..3586fd7 100644 --- a/stacosys/db/database.py +++ b/stacosys/db/database.py @@ -12,13 +12,13 @@ class BaseModel(Model): database = db -class Database: - def get_db(self): - return db +def setup(db_url): + db.init(db_url) + db.connect() - def setup(self, db_url): - db.init(db_url) - db.connect() + from stacosys.model.comment import Comment + db.create_tables([Comment], safe=True) - from stacosys.model.comment import Comment - db.create_tables([Comment], safe=True) + +def get_db(): + return db diff --git a/stacosys/interface/templates/login_fr.html b/stacosys/interface/templates/login_fr.html index e43d951..aa385d1 100644 --- a/stacosys/interface/templates/login_fr.html +++ b/stacosys/interface/templates/login_fr.html @@ -36,7 +36,7 @@
    -

    Cette page a été conçue par Yax avec Simple.css.

    +

    Cette page a été conçue avec Simple.css.

    diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index a282965..f5bedf2 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -33,10 +33,10 @@ def login(): if is_login_ok(username, password): session['user'] = username return redirect('/web/admin') - + # TODO localization flash("Identifiant ou mot de passe incorrect") return redirect('/web/login') - + # GET return render_template("login_" + app.config.get("LANG") + ".html") @@ -49,6 +49,7 @@ def logout(): @app.route("/web/admin", methods=["GET"]) def admin_homepage(): if not ('user' in session and session['user'] == app.config.get("WEB_USERNAME")): + # TODO localization flash("Vous avez été déconnecté.") return redirect('/web/login') @@ -59,8 +60,17 @@ def admin_homepage(): @app.route("/web/admin", methods=["POST"]) def admin_action(): - flash(request.form.get("comment") + " " + request.form.get("action")) - # TODO process action - # rebuild RSS - app.config.get("RSS").generate() + comment = dao.find_comment_by_id(request.form.get("comment")) + if comment is None: + # TODO localization + flash("Commentaire introuvable") + elif request.form.get("action") == "APPROVE": + dao.publish_comment(comment) + app.config.get("RSS").generate() + # TODO localization + flash("Commentaire publié") + else: + dao.delete_comment(comment) + # TODO localization + flash("Commentaire supprimé") return redirect('/web/admin') diff --git a/tests/test_api.py b/tests/test_api.py index b162153..641fd35 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,8 +22,7 @@ def init_test_db(): @pytest.fixture def client(): logger = logging.getLogger(__name__) - db = database.Database() - db.setup(":memory:") + database.setup(":memory:") init_test_db() app.config.update(SITE_TOKEN="ETC") logger.info(f"start interface {api}") diff --git a/tests/test_db.py b/tests/test_db.py index 2d99936..f3ecf57 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -7,8 +7,7 @@ from stacosys.db import database class DbTestCase(unittest.TestCase): def setUp(self): - db = database.Database() - db.setup(":memory:") + database.setup(":memory:") def test_dao_published(self): diff --git a/tests/test_form.py b/tests/test_form.py index 94bea7e..ff9fbfd 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -13,8 +13,7 @@ from stacosys.interface import form @pytest.fixture def client(): logger = logging.getLogger(__name__) - db = database.Database() - db.setup(":memory:") + database.setup(":memory:") app.config.update(SITE_REDIRECT="/redirect") logger.info(f"start interface {form}") return app.test_client() From fbeb0d386ee66519b09d6c8b742d2232bb66e512 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Feb 2022 12:09:56 +0100 Subject: [PATCH 423/586] README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 98b075e..2d25eb7 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,4 @@ Build and Dependency management relies on [Poetry](https://python-poetry.org/), ### Improvements Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. + From 6c4bbace0279dc121fb2b2e0825ad9fed5fd504a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Feb 2022 12:12:12 +0100 Subject: [PATCH 424/586] publish docker image 3.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a025b04..a9987d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.9-alpine -ARG STACOSYS_VERSION=2.1 +ARG STACOSYS_VERSION=3.0 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget From c2a90291ccb1d521a9c086279cb87dae60286bf3 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Apr 2022 18:29:59 +0200 Subject: [PATCH 425/586] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2d25eb7..98b075e 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,3 @@ Build and Dependency management relies on [Poetry](https://python-poetry.org/), ### Improvements Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. - From 16e74c281cb605c6ca514608deb3e18e86bbfc4d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 3 Apr 2022 18:45:00 +0200 Subject: [PATCH 426/586] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f5cfda..3388d87 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ workspace.code-workspace config-server.ini config-dev.ini .idea/ +.python-version From 04ad1674a918c3a05b75a55bea9c99cf739544d9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:39:17 +0200 Subject: [PATCH 427/586] add flake8 action --- .github/workflows/lint.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..57e1f06 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: Lint + +on: + push: + paths: + - '*.py' + +jobs: + flake8_py3: + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.9.9 + architecture: x64 + - name: Checkout Stacosys + uses: actions/checkout@master + - name: Install flake8 + run: pip install flake8 + - name: Run flake8 + uses: suo/flake8-github-action@releases/v1 + with: + checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 4ea176f82508e20512e9a86951f9993be002882c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:52:39 +0200 Subject: [PATCH 428/586] flake8 --- .github/workflows/lint.yml | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 57e1f06..0e50953 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint +name: lint on: push: @@ -7,15 +7,24 @@ on: jobs: flake8_py3: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.9.9] + poetry-version: [1.1.12] + os: [ubuntu-18.04] + runs-on: ${{ matrix.os }} steps: - - name: Setup Python - uses: actions/setup-python@v1 + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 with: - python-version: 3.9.9 - architecture: x64 - - name: Checkout Stacosys - uses: actions/checkout@master + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install - name: Install flake8 run: pip install flake8 - name: Run flake8 @@ -23,4 +32,4 @@ jobs: with: checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.ACTION_GITHUB_TOKEN }} \ No newline at end of file From 613ed6d8b02ad98c5b4911fe9ffc138f39e15d65 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:58:55 +0200 Subject: [PATCH 429/586] on push --- .github/workflows/lint.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0e50953..a4f5dcc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,9 +1,5 @@ name: lint - -on: - push: - paths: - - '*.py' +on: push jobs: flake8_py3: From 3f5dcb39a73c28b1aa5fd853d8c4fff5b6d6faa2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:20:54 +0200 Subject: [PATCH 430/586] black compliance --- .github/workflows/lint.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a4f5dcc..d8246dd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,11 +21,11 @@ jobs: poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: poetry install - - name: Install flake8 - run: pip install flake8 - - name: Run flake8 - uses: suo/flake8-github-action@releases/v1 + - uses: grantmcconnaughey/lintly-flake8-github-action@v1.0 with: - checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name - env: - GITHUB_TOKEN: ${{ secrets.ACTION_GITHUB_TOKEN }} \ No newline at end of file + # The GitHub API token to create reviews with + token: ${{ secrets.GITHUB_TOKEN }} + # Fail if "new" violations detected or "any", default "new" + failIf: new + # Additional arguments to be black compliant + args: "--max-line-length=88 --extend-ignore=E203 --exclude=*/tests/*" \ No newline at end of file From 443213abcfe42655f865ba6fd5dda2bbcf68f383 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:27:53 +0200 Subject: [PATCH 431/586] fix python version --- .github/workflows/lint.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d8246dd..fc7e573 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,23 +2,15 @@ name: lint on: push jobs: - flake8_py3: - strategy: - fail-fast: false - matrix: - python-version: [3.9.9] - poetry-version: [1.1.12] - os: [ubuntu-18.04] - runs-on: ${{ matrix.os }} + lint: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - name: Run image uses: abatilo/actions-poetry@v2.0.0 with: - poetry-version: ${{ matrix.poetry-version }} + poetry-version: 1.1.12 - name: Install dependencies run: poetry install - uses: grantmcconnaughey/lintly-flake8-github-action@v1.0 From 80c851e750bafdf66096e5aba11ec93f3b9b4910 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:31:32 +0200 Subject: [PATCH 432/586] no setup --- .github/workflows/lint.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc7e573..3415172 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,8 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - name: Run image + - name: Install poetry uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: 1.1.12 From 2d8ddcb3cb949d9b3cf29f832df9a9d7a264f37c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:33:59 +0200 Subject: [PATCH 433/586] setup github token --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3415172..cd20671 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - uses: grantmcconnaughey/lintly-flake8-github-action@v1.0 with: # The GitHub API token to create reviews with - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ACTION_GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant From dd53d646ce10ffac5f91e2e6027964a26445aa3b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:41:27 +0200 Subject: [PATCH 434/586] bypass action issue: https://github.com/grantmcconnaughey/lintly-flake8-github-action/issues/17 --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cd20671..0e87930 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,10 +12,10 @@ jobs: poetry-version: 1.1.12 - name: Install dependencies run: poetry install - - uses: grantmcconnaughey/lintly-flake8-github-action@v1.0 + - uses: usama2490/lintly-flake8-github-action@v1.1 with: # The GitHub API token to create reviews with - token: ${{ secrets.ACTION_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant From a20920a1c7e51623b3d9b233247bdec13dd83c70 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:44:24 +0200 Subject: [PATCH 435/586] conf token --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0e87930..4f59cad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - uses: usama2490/lintly-flake8-github-action@v1.1 with: # The GitHub API token to create reviews with - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ACTION_GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant From 339d9e5ddfd90e407e95d244957483caca58c618 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:53:38 +0200 Subject: [PATCH 436/586] fix perms --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4f59cad..0e87930 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - uses: usama2490/lintly-flake8-github-action@v1.1 with: # The GitHub API token to create reviews with - token: ${{ secrets.ACTION_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant From e2e1abcca648af5888e04b11a6457936d0473ace Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:03:47 +0200 Subject: [PATCH 437/586] Update lint.yml --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0e87930..939ad11 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,8 +15,8 @@ jobs: - uses: usama2490/lintly-flake8-github-action@v1.1 with: # The GitHub API token to create reviews with - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ACTION_GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant - args: "--max-line-length=88 --extend-ignore=E203 --exclude=*/tests/*" \ No newline at end of file + args: "--max-line-length=88 --extend-ignore=E203 --exclude=*/tests/*" From 2f23c523b10ec748cebf506fe6c24abba4a1b021 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:10:03 +0200 Subject: [PATCH 438/586] Update lint.yml --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 939ad11..190c8e8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - uses: usama2490/lintly-flake8-github-action@v1.1 with: # The GitHub API token to create reviews with - token: ${{ secrets.ACTION_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} # Fail if "new" violations detected or "any", default "new" failIf: new # Additional arguments to be black compliant From 68ea4204c2d1856635bff1c83d2884bd4a5f5bb5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:05:00 +0200 Subject: [PATCH 439/586] Create flake8.ini --- flake8.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 flake8.ini diff --git a/flake8.ini b/flake8.ini new file mode 100644 index 0000000..78f2d54 --- /dev/null +++ b/flake8.ini @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 +exclude = */tests/* From 3761b4ee9cbb9910d2a0e88a41f1dbfeb22ee707 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:05:06 +0200 Subject: [PATCH 440/586] Update lint.yml --- .github/workflows/lint.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 190c8e8..6e28ecd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,17 +6,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: "3.9.9" - name: Install poetry uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: 1.1.12 - name: Install dependencies run: poetry install - - uses: usama2490/lintly-flake8-github-action@v1.1 + - name: Run flake8 + uses: julianwachholz/flake8-action@v2 with: - # The GitHub API token to create reviews with - token: ${{ secrets.GITHUB_TOKEN }} - # Fail if "new" violations detected or "any", default "new" - failIf: new - # Additional arguments to be black compliant - args: "--max-line-length=88 --extend-ignore=E203 --exclude=*/tests/*" + checkName: "Python Lint" + path: path/to/files + plugins: flake8-spellcheck + config: flake8.ini + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6aba82a7ad689e2cc1345a264e86f48fdf5fb2da Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:10:21 +0200 Subject: [PATCH 441/586] Update lint.yml --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6e28ecd..5bbba5f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: uses: julianwachholz/flake8-action@v2 with: checkName: "Python Lint" - path: path/to/files + path: . plugins: flake8-spellcheck config: flake8.ini env: From 5a286fa0d2ae3747eca6e26d4a2bf5708081a11c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:26:58 +0200 Subject: [PATCH 442/586] Update flake8.ini --- flake8.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/flake8.ini b/flake8.ini index 78f2d54..01ad95a 100644 --- a/flake8.ini +++ b/flake8.ini @@ -2,3 +2,4 @@ max-line-length = 88 extend-ignore = E203 exclude = */tests/* +spellcheck-targets=comments From 325455439a1bb97c5b20ad9762deaa4f1878e5a9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 26 Apr 2022 20:51:42 +0200 Subject: [PATCH 443/586] black --- dbmigration/create_empty_db.py | 18 +- dbmigration/migrate_from_1.1_to_2.0.py | 20 +- poetry.lock | 498 +++++++------------------ pyproject.toml | 8 +- run.py | 5 +- stacosys.code-workspace | 8 + stacosys/core/mailer.py | 16 +- stacosys/core/rss.py | 22 +- stacosys/db/dao.py | 17 +- stacosys/db/database.py | 1 + stacosys/interface/form.py | 4 +- stacosys/interface/web/admin.py | 40 +- tests/test_api.py | 28 +- tests/test_config.py | 5 +- tests/test_db.py | 3 +- tests/test_form.py | 25 +- tests/test_stacosys.py | 1 - whitelist.txt | 1 + 18 files changed, 261 insertions(+), 459 deletions(-) create mode 100644 stacosys.code-workspace create mode 100644 whitelist.txt diff --git a/dbmigration/create_empty_db.py b/dbmigration/create_empty_db.py index a597869..987eab1 100644 --- a/dbmigration/create_empty_db.py +++ b/dbmigration/create_empty_db.py @@ -9,16 +9,16 @@ cursor = connection.cursor() script = """ CREATE TABLE comment ( id INTEGER NOT NULL PRIMARY KEY, - url VARCHAR(255) NOT NULL, - notified DATETIME, - created DATETIME NOT NULL, - published DATETIME, - author_name VARCHAR(255) NOT NULL, - author_site VARCHAR(255) NOT NULL, - author_gravatar varchar(255), - content TEXT NOT NULL + url VARCHAR(255) NOT NULL, + notified DATETIME, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, + author_site VARCHAR(255) NOT NULL, + author_gravatar varchar(255), + content TEXT NOT NULL , ulid INTEGER); """ cursor.executescript(script) -connection.close() \ No newline at end of file +connection.close() diff --git a/dbmigration/migrate_from_1.1_to_2.0.py b/dbmigration/migrate_from_1.1_to_2.0.py index 396f576..9870d25 100644 --- a/dbmigration/migrate_from_1.1_to_2.0.py +++ b/dbmigration/migrate_from_1.1_to_2.0.py @@ -6,7 +6,7 @@ import sqlite3 connection = sqlite3.connect("db.sqlite") cursor = connection.cursor() -# What script performs: +# What script performs: # - first, remove site table: crash here if table doesn't exist (compatibility test without effort) # - remove site_id colum from comment table script = """ @@ -16,14 +16,14 @@ DROP TABLE site; ALTER TABLE comment RENAME TO _comment_old; CREATE TABLE comment ( id INTEGER NOT NULL PRIMARY KEY, - url VARCHAR(255) NOT NULL, - notified DATETIME, - created DATETIME NOT NULL, - published DATETIME, - author_name VARCHAR(255) NOT NULL, - author_site VARCHAR(255) NOT NULL, - author_gravatar varchar(255), - content TEXT NOT NULL + url VARCHAR(255) NOT NULL, + notified DATETIME, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, + author_site VARCHAR(255) NOT NULL, + author_gravatar varchar(255), + content TEXT NOT NULL ); INSERT INTO comment (id, url, notified, created, published, author_name, author_site, author_gravatar, content) SELECT id, url, notified, created, published, author_name, author_site, author_gravatar, content @@ -34,4 +34,4 @@ PRAGMA foreign_keys = ON; """ cursor.executescript(script) -connection.close() \ No newline at end of file +connection.close() diff --git a/poetry.lock b/poetry.lock index a778f91..605f422 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,6 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "apscheduler" -version = "3.8.1" +version = "3.9.1" description = "In-process task scheduler with Cron-like capabilities" category = "main" optional = false @@ -27,7 +19,7 @@ mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["pytest (<6)", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -75,25 +67,25 @@ tzdata = ["tzdata"] [[package]] name = "black" -version = "20.8b1" +version = "22.3.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" @@ -116,11 +108,11 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.4" +version = "8.1.2" description = "Composable command line interface toolkit" -category = "main" +category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -203,34 +195,16 @@ pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "flake8-black" -version = "0.2.4" +version = "0.3.2" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] -black = "*" +black = ">=22.1.0" flake8 = ">=3.0.0" -toml = "*" - -[[package]] -name = "flask" -version = "2.0.3" -description = "A simple framework for building complex web applications." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -click = ">=7.1.2" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] +tomli = "*" [[package]] name = "idna" @@ -242,7 +216,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.1" +version = "4.11.3" description = "Read metadata from Python packages" category = "main" optional = false @@ -252,7 +226,7 @@ python-versions = ">=3.7" zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] @@ -264,28 +238,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "itsdangerous" -version = "2.1.0" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jinja2" -version = "3.0.3" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - [[package]] name = "markdown" version = "3.3.6" @@ -300,14 +252,6 @@ importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] testing = ["coverage", "pyyaml"] -[[package]] -name = "markupsafe" -version = "2.1.0" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "mccabe" version = "0.6.1" @@ -318,19 +262,21 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.790" +version = "0.942" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" @@ -361,7 +307,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "peewee" -version = "3.14.9" +version = "3.14.10" description = "a little orm" category = "main" optional = false @@ -369,15 +315,15 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.5.0" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -425,14 +371,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pyrss2gen" @@ -481,7 +427,7 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -499,14 +445,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" "backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} tzdata = {version = "*", markers = "python_version >= \"3.6\""} -[[package]] -name = "regex" -version = "2022.1.18" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" version = "2.27.1" @@ -525,17 +463,6 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] -[[package]] -name = "rope" -version = "0.16.0" -description = "a python refactoring library..." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -dev = ["pytest"] - [[package]] name = "six" version = "1.16.0" @@ -552,9 +479,17 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "tox" -version = "3.24.5" +version = "3.25.0" description = "tox is a generic virtualenv management and test command line tool" category = "main" optional = false @@ -574,25 +509,17 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tzdata" -version = "2021.5" +version = "2022.1" description = "Provider of IANA time zone data" category = "main" optional = false @@ -600,7 +527,7 @@ python-versions = ">=2" [[package]] name = "tzlocal" -version = "4.1" +version = "4.2" description = "tzinfo object for the local timezone" category = "main" optional = false @@ -617,20 +544,20 @@ test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.13.1" +version = "20.14.1" description = "Virtual Python Environment builder" category = "main" optional = false @@ -646,42 +573,27 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] -[[package]] -name = "werkzeug" -version = "2.0.3" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchdog = ["watchdog"] - [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "1db9a9c6f78a4148493fa7ab87741373fd6eccd7c078657de7dd00d3af8482b9" +content-hash = "98643b0bfca472ab7d13b291c2a45c5d4c4b94a8ca42c8e47f0dd92bb75fea98" [metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] apscheduler = [ - {file = "APScheduler-3.8.1-py2.py3-none-any.whl", hash = "sha256:c22cb14b411a31435eb2c530dfbbec948ac63015b517087c7978adb61b574865"}, - {file = "APScheduler-3.8.1.tar.gz", hash = "sha256:5cf344ebcfbdaa48ae178c029c055cec7bc7a4a47c21e315e4d1f08bd35f2355"}, + {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"}, + {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, @@ -714,7 +626,29 @@ background = [ {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -725,8 +659,8 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, + {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, + {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -806,98 +740,53 @@ flake8 = [ {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] flake8-black = [ - {file = "flake8-black-0.2.4.tar.gz", hash = "sha256:a7871bfd1cbff431a1fc91ba60ae154510c80f575e6b9a2bbb13dfb4650afd22"}, - {file = "flake8_black-0.2.4-py3-none-any.whl", hash = "sha256:0a70dfd97c8439827f365dc6dbc6c8c9cc087f0833625c6cc6848ff7876256be"}, -] -flask = [ - {file = "Flask-2.0.3-py3-none-any.whl", hash = "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f"}, - {file = "Flask-2.0.3.tar.gz", hash = "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d"}, + {file = "flake8-black-0.3.2.tar.gz", hash = "sha256:6f6bf198f3f45df43245d1d1a0ba2035ee5817d167680f9e1af23cde70cb7548"}, + {file = "flake8_black-0.3.2-py3-none-any.whl", hash = "sha256:9c86b24ede16a80555808c6c4779d81bc54cff57d0dd1980029c0e4b39ac8b45"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"}, - {file = "importlib_metadata-4.11.1.tar.gz", hash = "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -itsdangerous = [ - {file = "itsdangerous-2.1.0-py3-none-any.whl", hash = "sha256:29285842166554469a56d427addc0843914172343784cb909695fdbe90a3e129"}, - {file = "itsdangerous-2.1.0.tar.gz", hash = "sha256:d848fcb8bc7d507c4546b448574e8a44fc4ea2ba84ebf8d783290d53e81992f5"}, -] -jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, -] markdown = [ {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] -markupsafe = [ - {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"}, - {file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"}, - {file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"}, - {file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"}, - {file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"}, - {file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"}, -] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mypy = [ - {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, - {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, - {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, - {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, - {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, - {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, - {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, - {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, - {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, - {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, - {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, - {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, - {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, - {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, + {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, + {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, + {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, + {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, + {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, + {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, + {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, + {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, + {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, + {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, + {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, + {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, + {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, + {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, + {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, + {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, + {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -912,11 +801,11 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] peewee = [ - {file = "peewee-3.14.9.tar.gz", hash = "sha256:69c1b88dc89b184231cc1ce6df241075aca5cec43e89749cc4a63108f9ceea47"}, + {file = "peewee-3.14.10.tar.gz", hash = "sha256:23271422b332c82d30c92597dee905ee831b56c6d99c33e05901e6891c75fe15"}, ] platformdirs = [ - {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, - {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -938,8 +827,8 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, @@ -953,98 +842,17 @@ pytest-cov = [ {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] -regex = [ - {file = "regex-2022.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5"}, - {file = "regex-2022.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7"}, - {file = "regex-2022.1.18-cp310-cp310-win32.whl", hash = "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d"}, - {file = "regex-2022.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184"}, - {file = "regex-2022.1.18-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a"}, - {file = "regex-2022.1.18-cp36-cp36m-win32.whl", hash = "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3"}, - {file = "regex-2022.1.18-cp36-cp36m-win_amd64.whl", hash = "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633"}, - {file = "regex-2022.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a"}, - {file = "regex-2022.1.18-cp37-cp37m-win32.whl", hash = "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6"}, - {file = "regex-2022.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118"}, - {file = "regex-2022.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899"}, - {file = "regex-2022.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d"}, - {file = "regex-2022.1.18-cp38-cp38-win32.whl", hash = "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02"}, - {file = "regex-2022.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e"}, - {file = "regex-2022.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d"}, - {file = "regex-2022.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093"}, - {file = "regex-2022.1.18-cp39-cp39-win32.whl", hash = "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1"}, - {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, - {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, -] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] -rope = [ - {file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"}, - {file = "rope-0.16.0-py3-none-any.whl", hash = "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203"}, - {file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1053,67 +861,35 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tox = [ - {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, - {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +tox = [ + {file = "tox-3.25.0-py2.py3-none-any.whl", hash = "sha256:0805727eb4d6b049de304977dfc9ce315a1938e6619c3ab9f38682bb04662a5a"}, + {file = "tox-3.25.0.tar.gz", hash = "sha256:37888f3092aa4e9f835fc8cc6dadbaaa0782651c41ef359e3a5743fcb0308160"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] tzdata = [ - {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, - {file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"}, + {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, + {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, ] tzlocal = [ - {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, - {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, + {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, + {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] virtualenv = [ - {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, - {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, -] -werkzeug = [ - {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"}, - {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"}, + {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, + {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] diff --git a/pyproject.toml b/pyproject.toml index d7bf198..57711fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ apscheduler = "^3.6.3" pyrss2gen = "^1.1" profig = "^0.5.1" markdown = "^3.1.1" -Flask = "^2.0.1" requests = "^2.25.1" coverage = "^5.5" peewee = "^3.14.8" @@ -20,10 +19,9 @@ tox = "^3.24.5" background = "^0.2.1" [tool.poetry.dev-dependencies] -rope = "^0.16.0" -mypy = "^0.790" -flake8-black = "^0.2.1" -black = "^20.8b1" +mypy = "^0.942" +flake8-black = "^0.3.2" +black = "^22.3.0" pytest = "^6.2.4" pytest-cov = "^2.12.1" coveralls = "^3.2.0" diff --git a/run.py b/run.py index 948f8b6..33a833e 100644 --- a/run.py +++ b/run.py @@ -22,11 +22,8 @@ def configure_logging(level): root_logger.setLevel(level) ch = logging.StreamHandler() ch.setLevel(level) - # create formatter formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") - # add formatter to ch ch.setFormatter(formatter) - # add ch to logger root_logger.addHandler(ch) @@ -57,7 +54,7 @@ def stacosys_server(config_pathname): logger.info("Start Stacosys application") - # generate RSS for all sites + # generate RSS rss = Rss( conf.get(ConfigParameter.LANG), conf.get(ConfigParameter.RSS_FILE), diff --git a/stacosys.code-workspace b/stacosys.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/stacosys.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index defa09a..b58f75b 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -12,12 +12,12 @@ logger = logging.getLogger(__name__) class Mailer: def __init__( - self, - smtp_host, - smtp_port, - smtp_login, - smtp_password, - site_admin_email, + self, + smtp_host, + smtp_port, + smtp_login, + smtp_password, + site_admin_email, ): self._smtp_host = smtp_host self._smtp_port = smtp_port @@ -35,7 +35,9 @@ class Mailer: msg["From"] = sender context = ssl.create_default_context() - with smtplib.SMTP_SSL(self._smtp_host, self._smtp_port, context=context) as server: + with smtplib.SMTP_SSL( + self._smtp_host, self._smtp_port, context=context + ) as server: server.login(self._smtp_login, self._smtp_password) server.send_message(msg, sender, receivers) return True diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index c3e3e9a..958f155 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -11,12 +11,12 @@ from stacosys.model.comment import Comment class Rss: def __init__( - self, - lang, - rss_file, - rss_proto, - site_name, - site_url, + self, + lang, + rss_file, + rss_proto, + site_name, + site_url, ): self._lang = lang self._rss_file = rss_file @@ -29,16 +29,16 @@ class Rss: items = [] for row in ( - Comment.select() - .where(Comment.published) - .order_by(-Comment.published) - .limit(10) + Comment.select() + .where(Comment.published) + .order_by(-Comment.published) + .limit(10) ): item_link = "%s://%s%s" % (self._rss_proto, self._site_url, row.url) items.append( PyRSS2Gen.RSSItem( title="%s - %s://%s%s" - % (self._rss_proto, row.author_name, self._site_url, row.url), + % (self._rss_proto, row.author_name, self._site_url, row.url), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index e24fec2..597579d 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -34,14 +34,21 @@ def find_not_published_comments(): def find_published_comments_by_url(url): - return Comment.select(Comment).where((Comment.url == url) & (Comment.published.is_null(False))).order_by( - +Comment.published) + return ( + Comment.select(Comment) + .where((Comment.url == url) & (Comment.published.is_null(False))) + .order_by(+Comment.published) + ) def count_published_comments(url): - return Comment.select(Comment).where( - (Comment.url == url) & (Comment.published.is_null(False))).count() if url else Comment.select(Comment).where( - Comment.published.is_null(False)).count() + return ( + Comment.select(Comment) + .where((Comment.url == url) & (Comment.published.is_null(False))) + .count() + if url + else Comment.select(Comment).where(Comment.published.is_null(False)).count() + ) def create_comment(url, author_name, author_site, author_gravatar, message): diff --git a/stacosys/db/database.py b/stacosys/db/database.py index 3586fd7..73c2591 100644 --- a/stacosys/db/database.py +++ b/stacosys/db/database.py @@ -17,6 +17,7 @@ def setup(db_url): db.connect() from stacosys.model.comment import Comment + db.create_tables([Comment], safe=True) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 8bdabc0..6955d44 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -39,7 +39,9 @@ def new_form_comment(): abort(400) # add a row to Comment table - comment = dao.create_comment(url, author_name, author_site, author_gravatar, message) + comment = dao.create_comment( + url, author_name, author_site, author_gravatar, message + ) # send notification e-mail asynchronously submit_new_comment(comment) diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index f5bedf2..c65ee0a 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -17,45 +17,51 @@ app.add_url_rule("/web/", endpoint="index") @app.endpoint("index") def index(): - return redirect('/web/admin') + return redirect("/web/admin") def is_login_ok(username, password): hashed = hashlib.sha256(password.encode()).hexdigest().upper() - return app.config.get("WEB_USERNAME") == username and app.config.get("WEB_PASSWORD") == hashed + return ( + app.config.get("WEB_USERNAME") == username + and app.config.get("WEB_PASSWORD") == hashed + ) -@app.route('/web/login', methods=['POST', 'GET']) +@app.route("/web/login", methods=["POST", "GET"]) def login(): - if request.method == 'POST': - username = request.form.get('username') - password = request.form.get('password') + if request.method == "POST": + username = request.form.get("username") + password = request.form.get("password") if is_login_ok(username, password): - session['user'] = username - return redirect('/web/admin') + session["user"] = username + return redirect("/web/admin") # TODO localization flash("Identifiant ou mot de passe incorrect") - return redirect('/web/login') + return redirect("/web/login") # GET return render_template("login_" + app.config.get("LANG") + ".html") -@app.route('/web/logout', methods=["GET"]) +@app.route("/web/logout", methods=["GET"]) def logout(): - session.pop('user') - return redirect('/web/admin') + session.pop("user") + return redirect("/web/admin") @app.route("/web/admin", methods=["GET"]) def admin_homepage(): - if not ('user' in session and session['user'] == app.config.get("WEB_USERNAME")): + if not ("user" in session and session["user"] == app.config.get("WEB_USERNAME")): # TODO localization flash("Vous avez été déconnecté.") - return redirect('/web/login') + return redirect("/web/login") comments = dao.find_not_published_comments() - return render_template("admin_" + app.config.get("LANG") + ".html", comments=comments, - baseurl=app.config.get("SITE_URL")) + return render_template( + "admin_" + app.config.get("LANG") + ".html", + comments=comments, + baseurl=app.config.get("SITE_URL"), + ) @app.route("/web/admin", methods=["POST"]) @@ -73,4 +79,4 @@ def admin_action(): dao.delete_comment(comment) # TODO localization flash("Commentaire supprimé") - return redirect('/web/admin') + return redirect("/web/admin") diff --git a/tests/test_api.py b/tests/test_api.py index 641fd35..a2b7033 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -30,35 +30,35 @@ def client(): def test_api_ping(client): - resp = client.get('/api/ping') + resp = client.get("/api/ping") assert resp.data == b"OK" def test_api_count_global(client): - resp = client.get('/api/comments/count') + resp = client.get("/api/comments/count") d = json.loads(resp.data) - assert d and d['count'] == 2 + assert d and d["count"] == 2 def test_api_count_url(client): - resp = client.get('/api/comments/count?url=/site1') + resp = client.get("/api/comments/count?url=/site1") d = json.loads(resp.data) - assert d and d['count'] == 1 - resp = client.get('/api/comments/count?url=/site2') + assert d and d["count"] == 1 + resp = client.get("/api/comments/count?url=/site2") d = json.loads(resp.data) - assert d and d['count'] == 0 + assert d and d["count"] == 0 def test_api_comment(client): - resp = client.get('/api/comments?url=/site1') + resp = client.get("/api/comments?url=/site1") d = json.loads(resp.data) - assert d and len(d['data']) == 1 - comment = d['data'][0] - assert comment['author'] == 'Bob' - assert comment['content'] == 'comment 1' + assert d and len(d["data"]) == 1 + comment = d["data"][0] + assert comment["author"] == "Bob" + assert comment["content"] == "comment 1" def test_api_comment_not_found(client): - resp = client.get('/api/comments?url=/site2') + resp = client.get("/api/comments?url=/site2") d = json.loads(resp.data) - assert d and d['data'] == [] + assert d and d["data"] == [] diff --git a/tests/test_config.py b/tests/test_config.py index e98a9e8..07797c4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -11,7 +11,6 @@ EXPECTED_LANG = "fr" class ConfigTestCase(unittest.TestCase): - def setUp(self): self.conf = Config() self.conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) @@ -21,7 +20,9 @@ class ConfigTestCase(unittest.TestCase): self.assertTrue(self.conf.exists(ConfigParameter.DB_SQLITE_FILE)) def test_get(self): - self.assertEqual(self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE) + self.assertEqual( + self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE + ) self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) diff --git a/tests/test_db.py b/tests/test_db.py index f3ecf57..99eeb7e 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -5,7 +5,6 @@ from stacosys.db import database class DbTestCase(unittest.TestCase): - def setUp(self): database.setup(":memory:") @@ -50,5 +49,5 @@ class DbTestCase(unittest.TestCase): self.assertEqual(0, len(dao.find_not_notified_comments())) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_form.py b/tests/test_form.py index ff9fbfd..55ebda8 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -20,20 +20,25 @@ def client(): def test_new_comment_honeypot(client): - resp = client.post('/newcomment', - content_type='multipart/form-data', - data={'remarque': 'trapped'}) - assert resp.status == '400 BAD REQUEST' + resp = client.post( + "/newcomment", content_type="multipart/form-data", data={"remarque": "trapped"} + ) + assert resp.status == "400 BAD REQUEST" def test_new_comment_success(client): - resp = client.post('/newcomment', - content_type='multipart/form-data', - data={'author': 'Jack', 'url': '/site3', 'message': 'comment 3'}) - assert resp.status == '302 FOUND' + resp = client.post( + "/newcomment", + content_type="multipart/form-data", + data={"author": "Jack", "url": "/site3", "message": "comment 3"}, + ) + assert resp.status == "302 FOUND" def test_check_form_data(): from stacosys.interface.form import check_form_data - assert check_form_data({'author': 'Jack', 'url': '/site3', 'message': 'comment 3'}) - assert not check_form_data({'author': 'Jack', 'url': '/site3', 'message': 'comment 3', 'extra': 'ball'}) + + assert check_form_data({"author": "Jack", "url": "/site3", "message": "comment 3"}) + assert not check_form_data( + {"author": "Jack", "url": "/site3", "message": "comment 3", "extra": "ball"} + ) diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 23b2aaa..5625c8d 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -4,6 +4,5 @@ from stacosys import __version__ class StacosysTestCase(unittest.TestCase): - def test_version(self): self.assertEqual("2.0", __version__) diff --git a/whitelist.txt b/whitelist.txt new file mode 100644 index 0000000..a44b465 --- /dev/null +++ b/whitelist.txt @@ -0,0 +1 @@ +RSS \ No newline at end of file From c75fa2f669e166d9157c3212e7699de5893290e4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 27 Apr 2022 03:04:55 +0200 Subject: [PATCH 444/586] PEP8 --- README.md | 2 +- dbmigration/create_empty_db.py | 12 ++++++------ dbmigration/migrate_from_1.1_to_2.0.py | 20 +++++++++++--------- flake8.ini | 1 - tests/test_api.py | 1 + 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 98b075e..d350d06 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ## Stacosys diff --git a/dbmigration/create_empty_db.py b/dbmigration/create_empty_db.py index 987eab1..50bd0f2 100644 --- a/dbmigration/create_empty_db.py +++ b/dbmigration/create_empty_db.py @@ -8,15 +8,15 @@ cursor = connection.cursor() script = """ CREATE TABLE comment ( - id INTEGER NOT NULL PRIMARY KEY, - url VARCHAR(255) NOT NULL, + id INTEGER NOT NULL PRIMARY KEY, + url VARCHAR(255) NOT NULL, notified DATETIME, - created DATETIME NOT NULL, - published DATETIME, - author_name VARCHAR(255) NOT NULL, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, author_site VARCHAR(255) NOT NULL, author_gravatar varchar(255), - content TEXT NOT NULL + content TEXT NOT NULL , ulid INTEGER); """ diff --git a/dbmigration/migrate_from_1.1_to_2.0.py b/dbmigration/migrate_from_1.1_to_2.0.py index 9870d25..e86c433 100644 --- a/dbmigration/migrate_from_1.1_to_2.0.py +++ b/dbmigration/migrate_from_1.1_to_2.0.py @@ -8,25 +8,27 @@ cursor = connection.cursor() # What script performs: # - first, remove site table: crash here if table doesn't exist (compatibility test without effort) -# - remove site_id colum from comment table +# - remove site_id column from comment table script = """ PRAGMA foreign_keys = OFF; BEGIN TRANSACTION; DROP TABLE site; ALTER TABLE comment RENAME TO _comment_old; CREATE TABLE comment ( - id INTEGER NOT NULL PRIMARY KEY, - url VARCHAR(255) NOT NULL, + id INTEGER NOT NULL PRIMARY KEY, + url VARCHAR(255) NOT NULL, notified DATETIME, - created DATETIME NOT NULL, - published DATETIME, - author_name VARCHAR(255) NOT NULL, + created DATETIME NOT NULL, + published DATETIME, + author_name VARCHAR(255) NOT NULL, author_site VARCHAR(255) NOT NULL, author_gravatar varchar(255), - content TEXT NOT NULL + content TEXT NOT NULL ); -INSERT INTO comment (id, url, notified, created, published, author_name, author_site, author_gravatar, content) - SELECT id, url, notified, created, published, author_name, author_site, author_gravatar, content +INSERT INTO comment (id, url, notified, created, published, + author_name, author_site, author_gravatar, content) + SELECT id, url, notified, created, published, + author_name, author_site, author_gravatar, content FROM _comment_old; DROP TABLE _comment_old; COMMIT; diff --git a/flake8.ini b/flake8.ini index 01ad95a..9b8e5e8 100644 --- a/flake8.ini +++ b/flake8.ini @@ -1,5 +1,4 @@ [flake8] max-line-length = 88 extend-ignore = E203 -exclude = */tests/* spellcheck-targets=comments diff --git a/tests/test_api.py b/tests/test_api.py index a2b7033..6cb2196 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,6 +17,7 @@ def init_test_db(): c3 = dao.create_comment("/site3", "Jack", "/jack.site", "", "comment 3") dao.publish_comment(c1) dao.publish_comment(c3) + assert c2 @pytest.fixture From 8e51d7b745ef3cc63f538eb657dcf7a341d8c1dd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 27 Apr 2022 03:12:01 +0200 Subject: [PATCH 445/586] PEP8 --- dbmigration/migrate_from_1.1_to_2.0.py | 3 +- poetry.lock | 122 ++++++++++++++++++++++++- pyproject.toml | 1 + stacosys/interface/__init__.py | 2 +- 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/dbmigration/migrate_from_1.1_to_2.0.py b/dbmigration/migrate_from_1.1_to_2.0.py index e86c433..f63112e 100644 --- a/dbmigration/migrate_from_1.1_to_2.0.py +++ b/dbmigration/migrate_from_1.1_to_2.0.py @@ -7,7 +7,8 @@ connection = sqlite3.connect("db.sqlite") cursor = connection.cursor() # What script performs: -# - first, remove site table: crash here if table doesn't exist (compatibility test without effort) +# - first, remove site table: crash here if table doesn't exist +# (compatibility test without effort) # - remove site_id column from comment table script = """ PRAGMA foreign_keys = OFF; diff --git a/poetry.lock b/poetry.lock index 605f422..071218e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,7 +110,7 @@ unicode_backport = ["unicodedata2"] name = "click" version = "8.1.2" description = "Composable command line interface toolkit" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -206,6 +206,25 @@ black = ">=22.1.0" flake8 = ">=3.0.0" tomli = "*" +[[package]] +name = "flask" +version = "2.1.1" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + [[package]] name = "idna" version = "3.3" @@ -238,6 +257,28 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jinja2" +version = "3.1.1" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "markdown" version = "3.3.6" @@ -252,6 +293,14 @@ importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] testing = ["coverage", "pyyaml"] +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "mccabe" version = "0.6.1" @@ -573,6 +622,17 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +[[package]] +name = "werkzeug" +version = "2.1.1" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +watchdog = ["watchdog"] + [[package]] name = "zipp" version = "3.8.0" @@ -588,7 +648,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "98643b0bfca472ab7d13b291c2a45c5d4c4b94a8ca42c8e47f0dd92bb75fea98" +content-hash = "175178de9d664153a3380ed1e0065b7f29eab769052eff1d98e0df7f50de4754" [metadata.files] apscheduler = [ @@ -743,6 +803,10 @@ flake8-black = [ {file = "flake8-black-0.3.2.tar.gz", hash = "sha256:6f6bf198f3f45df43245d1d1a0ba2035ee5817d167680f9e1af23cde70cb7548"}, {file = "flake8_black-0.3.2-py3-none-any.whl", hash = "sha256:9c86b24ede16a80555808c6c4779d81bc54cff57d0dd1980029c0e4b39ac8b45"}, ] +flask = [ + {file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"}, + {file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -755,10 +819,60 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +itsdangerous = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] +jinja2 = [ + {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, + {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, +] markdown = [ {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -889,6 +1003,10 @@ virtualenv = [ {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, ] +werkzeug = [ + {file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"}, + {file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"}, +] zipp = [ {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, diff --git a/pyproject.toml b/pyproject.toml index 57711fb..f30c78f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ coverage = "^5.5" peewee = "^3.14.8" tox = "^3.24.5" background = "^0.2.1" +Flask = "^2.1.1" [tool.poetry.dev-dependencies] mypy = "^0.942" diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index ee1c258..540e40e 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -6,4 +6,4 @@ from flask import Flask app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! -app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' \ No newline at end of file +app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' From b1818958f576815d3336da4b24e2e5405ba56833 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 27 Apr 2022 03:17:36 +0200 Subject: [PATCH 446/586] Update README badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d350d06..6409247 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) - [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Flask version](https://img.shields.io/badge/Flask-2.0.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14.0-green.svg)](https://docs.peewee-orm.com/) + [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14-green.svg)](https://docs.peewee-orm.com/) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) ## Stacosys From be41d4d4440e54015efb849e90c2d903cb38d9be Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Oct 2022 17:29:16 +0200 Subject: [PATCH 447/586] Update dependencies --- poetry.lock | 308 ++++++++++++++++++++++++++-------------------------- 1 file changed, 152 insertions(+), 156 deletions(-) diff --git a/poetry.lock b/poetry.lock index 071218e..3585c2c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,7 +26,7 @@ zookeeper = ["kazoo"] [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false @@ -34,17 +34,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.4.0" +version = "22.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "background" @@ -67,18 +67,18 @@ tzdata = ["tzdata"] [[package]] name = "black" -version = "22.3.0" +version = "22.10.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -89,26 +89,26 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.1.2" +version = "8.1.3" description = "Composable command line interface toolkit" category = "main" optional = false @@ -119,7 +119,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.5" description = "Cross-platform colored terminal text." category = "main" optional = false @@ -154,7 +154,7 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "distlib" -version = "0.3.4" +version = "0.3.6" description = "Distribution utilities" category = "main" optional = false @@ -170,32 +170,32 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.6.0" +version = "3.8.0" description = "A platform independent file lock." category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] [[package]] name = "flake8" -version = "4.0.1" +version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "flake8-black" -version = "0.3.2" +version = "0.3.3" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false @@ -208,7 +208,7 @@ tomli = "*" [[package]] name = "flask" -version = "2.1.1" +version = "2.2.2" description = "A simple framework for building complex web applications." category = "main" optional = false @@ -219,7 +219,7 @@ click = ">=8.0" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.0" Jinja2 = ">=3.0" -Werkzeug = ">=2.0" +Werkzeug = ">=2.2.2" [package.extras] async = ["asgiref (>=3.2)"] @@ -227,7 +227,7 @@ dotenv = ["python-dotenv"] [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -235,7 +235,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.3" +version = "5.0.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -245,9 +245,9 @@ python-versions = ">=3.7" zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -267,7 +267,7 @@ python-versions = ">=3.7" [[package]] name = "jinja2" -version = "3.1.1" +version = "3.1.2" description = "A very fast and expressive template engine." category = "main" optional = false @@ -281,11 +281,11 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.3.6" +version = "3.4.1" description = "Python implementation of Markdown." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} @@ -303,11 +303,11 @@ python-versions = ">=3.7" [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "mypy" @@ -348,15 +348,15 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" -version = "0.9.0" +version = "0.10.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" [[package]] name = "peewee" -version = "3.14.10" +version = "3.15.3" description = "a little orm" category = "main" optional = false @@ -404,23 +404,23 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" -version = "2.8.0" +version = "2.9.1" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [[package]] name = "pyflakes" -version = "2.4.0" +version = "2.5.0" description = "passive checker of Python programs" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "pyparsing" -version = "3.0.8" +version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false @@ -476,7 +476,7 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytz" -version = "2022.1" +version = "2022.4" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -496,21 +496,21 @@ tzdata = {version = "*", markers = "python_version >= \"3.6\""} [[package]] name = "requests" -version = "2.27.1" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "six" @@ -524,7 +524,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -532,13 +532,13 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" [[package]] name = "tox" -version = "3.25.0" +version = "3.26.0" description = "tox is a generic virtualenv management and test command line tool" category = "main" optional = false @@ -551,7 +551,7 @@ packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" -toml = ">=0.9.4" +tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] @@ -560,7 +560,7 @@ testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytes [[package]] name = "typing-extensions" -version = "4.2.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false @@ -568,7 +568,7 @@ python-versions = ">=3.7" [[package]] name = "tzdata" -version = "2022.1" +version = "2022.5" description = "Provider of IANA time zone data" category = "main" optional = false @@ -593,57 +593,59 @@ test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.14.1" +version = "20.16.5" description = "Virtual Python Environment builder" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -platformdirs = ">=2,<3" -six = ">=1.9.0,<2" +distlib = ">=0.3.5,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] [[package]] name = "werkzeug" -version = "2.1.1" +version = "2.2.2" description = "The comprehensive WSGI web application library." category = "main" optional = false python-versions = ">=3.7" +[package.dependencies] +MarkupSafe = ">=2.1.1" + [package.extras] watchdog = ["watchdog"] [[package]] name = "zipp" -version = "3.8.0" +version = "3.9.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" @@ -656,12 +658,11 @@ apscheduler = [ {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"}, ] atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] background = [ {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, @@ -686,45 +687,43 @@ background = [ {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] black = [ - {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, - {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, - {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, - {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, - {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, - {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, - {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, - {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, - {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, - {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, - {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, - {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, - {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, - {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, - {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, - {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, - {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, - {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, - {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, - {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, - {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] click = [ - {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, - {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, @@ -785,35 +784,35 @@ coveralls = [ {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] distlib = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] filelock = [ - {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, - {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, ] flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] flake8-black = [ - {file = "flake8-black-0.3.2.tar.gz", hash = "sha256:6f6bf198f3f45df43245d1d1a0ba2035ee5817d167680f9e1af23cde70cb7548"}, - {file = "flake8_black-0.3.2-py3-none-any.whl", hash = "sha256:9c86b24ede16a80555808c6c4779d81bc54cff57d0dd1980029c0e4b39ac8b45"}, + {file = "flake8-black-0.3.3.tar.gz", hash = "sha256:8211f5e20e954cb57c709acccf2f3281ce27016d4c4b989c3e51f878bb7ce12a"}, + {file = "flake8_black-0.3.3-py3-none-any.whl", hash = "sha256:7d667d0059fd1aa468de1669d77cc934b7f1feeac258d57bdae69a8e73c4cd90"}, ] flask = [ - {file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"}, - {file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"}, + {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, + {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, - {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, + {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, + {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -824,12 +823,12 @@ itsdangerous = [ {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] jinja2 = [ - {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, - {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] markdown = [ - {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, - {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, + {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, + {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"}, ] markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, @@ -874,8 +873,8 @@ markupsafe = [ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] mypy = [ {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, @@ -911,11 +910,11 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, + {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, + {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, ] peewee = [ - {file = "peewee-3.14.10.tar.gz", hash = "sha256:23271422b332c82d30c92597dee905ee831b56c6d99c33e05901e6891c75fe15"}, + {file = "peewee-3.15.3.tar.gz", hash = "sha256:cc934286d0c0842203abe66a3c6583d1463371e633b03d6da054d0f74e70706f"}, ] platformdirs = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, @@ -933,16 +932,16 @@ py = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] pyparsing = [ - {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, - {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, @@ -956,17 +955,14 @@ pytest-cov = [ {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, + {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, + {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, ] pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] +requests = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -980,34 +976,34 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tox = [ - {file = "tox-3.25.0-py2.py3-none-any.whl", hash = "sha256:0805727eb4d6b049de304977dfc9ce315a1938e6619c3ab9f38682bb04662a5a"}, - {file = "tox-3.25.0.tar.gz", hash = "sha256:37888f3092aa4e9f835fc8cc6dadbaaa0782651c41ef359e3a5743fcb0308160"}, + {file = "tox-3.26.0-py2.py3-none-any.whl", hash = "sha256:bf037662d7c740d15c9924ba23bb3e587df20598697bb985ac2b49bdc2d847f6"}, + {file = "tox-3.26.0.tar.gz", hash = "sha256:44f3c347c68c2c68799d7d44f1808f9d396fc8a1a500cbc624253375c7ae107e"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] tzdata = [ - {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, - {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, + {file = "tzdata-2022.5-py2.py3-none-any.whl", hash = "sha256:323161b22b7802fdc78f20ca5f6073639c64f1a7227c40cd3e19fd1d0ce6650a"}, + {file = "tzdata-2022.5.tar.gz", hash = "sha256:e15b2b3005e2546108af42a0eb4ccab4d9e225e2dfbf4f77aad50c70a4b1f3ab"}, ] tzlocal = [ {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] virtualenv = [ - {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, - {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, + {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, + {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, ] werkzeug = [ - {file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"}, - {file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"}, + {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, + {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, + {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, ] From d5e676256a5e36ba0688116c027caeb8ffce0614 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:00:44 +0200 Subject: [PATCH 448/586] Upgrade deps --- poetry.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3585c2c..a89bd78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -976,8 +976,8 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tox = [ - {file = "tox-3.26.0-py2.py3-none-any.whl", hash = "sha256:bf037662d7c740d15c9924ba23bb3e587df20598697bb985ac2b49bdc2d847f6"}, - {file = "tox-3.26.0.tar.gz", hash = "sha256:44f3c347c68c2c68799d7d44f1808f9d396fc8a1a500cbc624253375c7ae107e"}, + {file = "tox-3.27.0-py2.py3-none-any.whl", hash = "sha256:89e4bc6df3854e9fc5582462e328dd3660d7d865ba625ae5881bbc63836a6324"}, + {file = "tox-3.27.0.tar.gz", hash = "sha256:d2c945f02a03d4501374a3d5430877380deb69b218b1df9b7f1d2f2a10befaf9"}, ] typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, From a8750d51a9dc6d2e2e19b15e8599c00030aeebb8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:05:03 +0200 Subject: [PATCH 449/586] Update deps --- poetry.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index a89bd78..823174a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -119,11 +119,11 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" @@ -476,7 +476,7 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytz" -version = "2022.4" +version = "2022.5" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -538,7 +538,7 @@ python-versions = ">=3.7" [[package]] name = "tox" -version = "3.26.0" +version = "3.27.0" description = "tox is a generic virtualenv management and test command line tool" category = "main" optional = false @@ -606,19 +606,19 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.16.5" +version = "20.16.6" description = "Virtual Python Environment builder" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -distlib = ">=0.3.5,<1" +distlib = ">=0.3.6,<1" filelock = ">=3.4.1,<4" platformdirs = ">=2.4,<3" [package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] [[package]] @@ -637,7 +637,7 @@ watchdog = ["watchdog"] [[package]] name = "zipp" -version = "3.9.0" +version = "3.10.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -722,8 +722,8 @@ click = [ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, @@ -955,8 +955,8 @@ pytest-cov = [ {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytz = [ - {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, - {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, + {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"}, + {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"}, ] pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, @@ -996,14 +996,14 @@ urllib3 = [ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] virtualenv = [ - {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, - {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, + {file = "virtualenv-20.16.6-py3-none-any.whl", hash = "sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108"}, + {file = "virtualenv-20.16.6.tar.gz", hash = "sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e"}, ] werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] zipp = [ - {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, - {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, + {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, + {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, ] From 4403ade05397a9be2d4f62c321db41a4587c387a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 12:33:40 +0100 Subject: [PATCH 450/586] requires Python 3.10 --- poetry.lock | 164 +++++++++++++++---------------------------------- pyproject.toml | 3 +- 2 files changed, 52 insertions(+), 115 deletions(-) diff --git a/poetry.lock b/poetry.lock index 823174a..b709db6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,6 +8,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.dependencies] pytz = "*" +setuptools = ">=0.7" six = ">=1.4.0" tzlocal = ">=2.0,<3.0.0 || >=4.0.0" @@ -19,7 +20,7 @@ mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +testing = ["mock", "pytest", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -41,10 +42,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "background" @@ -54,17 +55,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "backports.zoneinfo" -version = "0.2.1" -description = "Backport of the standard library zoneinfo module" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -tzdata = ["tzdata"] - [[package]] name = "black" version = "22.10.0" @@ -79,7 +69,6 @@ mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -104,7 +93,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "click" @@ -216,7 +205,6 @@ python-versions = ">=3.7" [package.dependencies] click = ">=8.0" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.0" Jinja2 = ">=3.0" Werkzeug = ">=2.2.2" @@ -233,22 +221,6 @@ category = "main" optional = false python-versions = ">=3.5" -[[package]] -name = "importlib-metadata" -version = "5.0.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] - [[package]] name = "iniconfig" version = "1.1.1" @@ -287,9 +259,6 @@ category = "main" optional = false python-versions = ">=3.7" -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - [package.extras] testing = ["coverage", "pyyaml"] @@ -356,7 +325,7 @@ python-versions = ">=3.7" [[package]] name = "peewee" -version = "3.15.3" +version = "3.15.4" description = "a little orm" category = "main" optional = false @@ -364,15 +333,15 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.5.3" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -386,14 +355,6 @@ python-versions = ">=3.6" dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "profig" -version = "0.5.1" -description = "A configuration library." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "py" version = "1.11.0" @@ -427,7 +388,7 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyrss2gen" @@ -472,11 +433,11 @@ pytest = ">=4.6" toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytz" -version = "2022.5" +version = "2022.6" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -491,7 +452,6 @@ optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" [package.dependencies] -"backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} tzdata = {version = "*", markers = "python_version >= \"3.6\""} [[package]] @@ -510,7 +470,20 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "65.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -556,7 +529,7 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] [[package]] name = "typing-extensions" @@ -568,7 +541,7 @@ python-versions = ">=3.7" [[package]] name = "tzdata" -version = "2022.5" +version = "2022.6" description = "Provider of IANA time zone data" category = "main" optional = false @@ -583,13 +556,12 @@ optional = false python-versions = ">=3.6" [package.dependencies] -"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} pytz-deprecation-shim = "*" tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] -test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] +test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] [[package]] name = "urllib3" @@ -600,8 +572,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -635,22 +607,10 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] -[[package]] -name = "zipp" -version = "3.10.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "1.1" -python-versions = "^3.8" -content-hash = "175178de9d664153a3380ed1e0065b7f29eab769052eff1d98e0df7f50de4754" +python-versions = "~3.10" +content-hash = "73950b6c92d30141cf0ce175dce5cf45d2354c3c013c692e6cc1587ec2d3be1d" [metadata.files] apscheduler = [ @@ -668,24 +628,6 @@ background = [ {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, ] -"backports.zoneinfo" = [ - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, - {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, -] black = [ {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, @@ -810,10 +752,6 @@ idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -importlib-metadata = [ - {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, - {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, -] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -914,19 +852,16 @@ pathspec = [ {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, ] peewee = [ - {file = "peewee-3.15.3.tar.gz", hash = "sha256:cc934286d0c0842203abe66a3c6583d1463371e633b03d6da054d0f74e70706f"}, + {file = "peewee-3.15.4.tar.gz", hash = "sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"}, ] platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, + {file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"}, + {file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -profig = [ - {file = "profig-0.5.1.tar.gz", hash = "sha256:cb9c094325a93505fc6325d13f3e679b281093223f143a96a6df8ad9c2bfc9a6"}, -] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -955,14 +890,21 @@ pytest-cov = [ {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytz = [ - {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"}, - {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"}, + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, ] pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] -requests = [] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +setuptools = [ + {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, + {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -984,8 +926,8 @@ typing-extensions = [ {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] tzdata = [ - {file = "tzdata-2022.5-py2.py3-none-any.whl", hash = "sha256:323161b22b7802fdc78f20ca5f6073639c64f1a7227c40cd3e19fd1d0ce6650a"}, - {file = "tzdata-2022.5.tar.gz", hash = "sha256:e15b2b3005e2546108af42a0eb4ccab4d9e225e2dfbf4f77aad50c70a4b1f3ab"}, + {file = "tzdata-2022.6-py2.py3-none-any.whl", hash = "sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342"}, + {file = "tzdata-2022.6.tar.gz", hash = "sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae"}, ] tzlocal = [ {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, @@ -1003,7 +945,3 @@ werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] -zipp = [ - {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, - {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, -] diff --git a/pyproject.toml b/pyproject.toml index f30c78f..8ecb202 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,9 @@ readme = "README.md" include = ["run.py"] [tool.poetry.dependencies] -python = "^3.8" +python = "~3.10" apscheduler = "^3.6.3" pyrss2gen = "^1.1" -profig = "^0.5.1" markdown = "^3.1.1" requests = "^2.25.1" coverage = "^5.5" From 0741bd182e12bc59cb45b92f9b3f8b5558b3ea0a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 12:51:25 +0100 Subject: [PATCH 451/586] use configparser and remove profig lib --- stacosys/conf/config.py | 41 ++++++++++++++++++++++++++++++----------- tests/test_config.py | 5 +++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 4e77fb1..f88fcbf 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -3,7 +3,7 @@ from enum import Enum -import profig +import configparser class ConfigParameter(Enum): @@ -32,32 +32,51 @@ class ConfigParameter(Enum): class Config: def __init__(self): - self._params = dict() + self._cfg = configparser.ConfigParser() @classmethod def load(cls, config_pathname): - cfg = profig.Config(config_pathname) - cfg.sync() config = cls() - config._params.update(cfg) + config._cfg.read(config_pathname) return config + def _split_key(self, key: ConfigParameter): + section, param = str(key.value).split(".") + if not param: + param = section + section = None + return (section, param) + def exists(self, key: ConfigParameter): - return key.value in self._params + section, param = self._split_key(key) + return self._cfg.has_option(section, param) def get(self, key: ConfigParameter): - return self._params[key.value] if key.value in self._params else None + section, param = self._split_key(key) + return ( + self._cfg.get(section, param) + if self._cfg.has_option(section, param) + else None + ) def put(self, key: ConfigParameter, value): - self._params[key.value] = value + section, param = self._split_key(key) + if section and not self._cfg.has_section(section): + self._cfg.add_section(section) + self._cfg.set(section, param, str(value)) def get_int(self, key: ConfigParameter): - return int(self._params[key.value]) + value = self.get(key) + return int(value) if value else 0 def get_bool(self, key: ConfigParameter): - value = self._params[key.value].lower() + value = self.get(key) assert value in ("yes", "true", "no", "false") return value in ("yes", "true") def __repr__(self): - return self._params.__repr__() + d = dict() + for section in self._cfg.sections(): + for option in self._cfg.options(section): + d[".".join([section, option])] = self._cfg.get(section, option) + return str(d) diff --git a/tests/test_config.py b/tests/test_config.py index 07797c4..e098965 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -23,9 +23,10 @@ class ConfigTestCase(unittest.TestCase): self.assertEqual( self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE ) - self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) - self.assertEqual(self.conf.get(ConfigParameter.HTTP_PORT), EXPECTED_HTTP_PORT) + self.assertEqual( + self.conf.get(ConfigParameter.HTTP_PORT), str(EXPECTED_HTTP_PORT) + ) self.assertEqual(self.conf.get_int(ConfigParameter.HTTP_PORT), 8080) try: self.conf.get_bool(ConfigParameter.DB_SQLITE_FILE) From 6f401ed3b7ad2ec065e747f4637c9025ff62fc54 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 21:20:51 +0100 Subject: [PATCH 452/586] CI --- .github/workflows/docker.yml | 5 +++- .github/workflows/lint.yml | 41 +++++++++++++++++--------------- .github/workflows/pytest.yml | 45 +++++++++++++++++++----------------- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bbee4cd..2637cbb 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,7 +6,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5bbba5f..b62dc12 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,22 +5,25 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: "3.9.9" - - name: Install poetry - uses: abatilo/actions-poetry@v2.0.0 - with: - poetry-version: 1.1.12 - - name: Install dependencies - run: poetry install - - name: Run flake8 - uses: julianwachholz/flake8-action@v2 - with: - checkName: "Python Lint" - path: . - plugins: flake8-spellcheck - config: flake8.ini - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/checkout@v3 + - uses: actions/setup-python@v2 + with: + python-version: "3.10.8" + - name: Install poetry + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: 1.2.2 + - name: Install dependencies + run: poetry install + - name: Run flake8 + uses: julianwachholz/flake8-action@v2 + with: + checkName: "Python Lint" + path: . + plugins: flake8-spellcheck + config: flake8.ini + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ff50a69..b01758a 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -6,26 +6,29 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8.10, 3.9.9] - poetry-version: [1.1.12] - os: [ubuntu-18.04, macos-latest, windows-latest] + python-version: [3.10.8] + poetry-version: [1.2.2] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Run image - uses: abatilo/actions-poetry@v2.0.0 - with: - poetry-version: ${{ matrix.poetry-version }} - - name: Install dependencies - run: poetry install - - name: Pytest and Coverage - run: | - poetry run coverage run -m --source=stacosys pytest tests - poetry run coverage report - - name: Send report to Coveralls - run: poetry run coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/checkout@v3 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install + - name: Pytest and Coverage + run: | + poetry run coverage run -m --source=stacosys pytest tests + poetry run coverage report + - name: Send report to Coveralls + run: poetry run coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} From 3e572e550c48084750a17803dd1f3ffa15f02774 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 21:34:43 +0100 Subject: [PATCH 453/586] lint --- stacosys/conf/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index f88fcbf..1de3ce0 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -37,10 +37,10 @@ class Config: @classmethod def load(cls, config_pathname): config = cls() - config._cfg.read(config_pathname) + config._cfg.read(config_pathname) return config - def _split_key(self, key: ConfigParameter): + def _split_key(self, key: ConfigParameter): section, param = str(key.value).split(".") if not param: param = section @@ -61,7 +61,7 @@ class Config: def put(self, key: ConfigParameter, value): section, param = self._split_key(key) - if section and not self._cfg.has_section(section): + if section and not self._cfg.has_section(section): self._cfg.add_section(section) self._cfg.set(section, param, str(value)) @@ -78,5 +78,5 @@ class Config: d = dict() for section in self._cfg.sections(): for option in self._cfg.options(section): - d[".".join([section, option])] = self._cfg.get(section, option) + d[".".join([section, option])] = self._cfg.get(section, option) return str(d) From bc193c53705dcaab3d7cba51a4f260d66bfde774 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 21:54:39 +0100 Subject: [PATCH 454/586] Release 3.1 --- Dockerfile | 4 ++-- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a9987d9..edddb8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM python:3.9-alpine +FROM python:3.10-alpine -ARG STACOSYS_VERSION=3.0 +ARG STACOSYS_VERSION=3.1 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget diff --git a/poetry.lock b/poetry.lock index b709db6..2046450 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,10 @@ [[package]] name = "apscheduler" -version = "3.9.1" +version = "3.9.1.post1" description = "In-process task scheduler with Cron-like capabilities" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = "!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.dependencies] pytz = "*" @@ -614,8 +614,8 @@ content-hash = "73950b6c92d30141cf0ce175dce5cf45d2354c3c013c692e6cc1587ec2d3be1d [metadata.files] apscheduler = [ - {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"}, - {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"}, + {file = "APScheduler-3.9.1.post1-py2.py3-none-any.whl", hash = "sha256:c8c618241dbb2785ed5a687504b14cb1851d6f7b5a4edf3a51e39cc6a069967a"}, + {file = "APScheduler-3.9.1.post1.tar.gz", hash = "sha256:b2bea0309569da53a7261bfa0ce19c67ddbfe151bda776a6a907579fdbd3eb2a"}, ] atomicwrites = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, diff --git a/pyproject.toml b/pyproject.toml index 8ecb202..889c98d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "3.0" +version = "3.1" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" From f43df83074fd843eecc0f1d1e4db80601174d373 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 21:59:47 +0100 Subject: [PATCH 455/586] Update badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6409247..a1875fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) - [![Python version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14-green.svg)](https://docs.peewee-orm.com/) + [![Python version](https://img.shields.io/badge/Python-3.10-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14-green.svg)](https://docs.peewee-orm.com/) [![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) From 57d6039e18122bda973248d36f503296ea3d6fa1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 22:11:22 +0100 Subject: [PATCH 456/586] upgrade github actions --- .github/workflows/docker.yml | 3 --- .github/workflows/lint.yml | 7 ++----- .github/workflows/pytest.yml | 7 ++----- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2637cbb..5ef1f7e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,9 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: '16' - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b62dc12..6b63472 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,15 +5,12 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v3 - with: - node-version: '16' - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: "3.10.8" - name: Install poetry - uses: abatilo/actions-poetry@v2.0.0 + uses: abatilo/actions-poetry@v2 with: poetry-version: 1.2.2 - name: Install dependencies diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b01758a..0293bc6 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -11,15 +11,12 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/setup-node@v3 - with: - node-version: '16' - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Run image - uses: abatilo/actions-poetry@v2.0.0 + uses: abatilo/actions-poetry@v2 with: poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies From f8beb5f859469e41abb43925b8929c41ff4837b4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 12 Nov 2022 22:32:13 +0100 Subject: [PATCH 457/586] check configuration --- run.py | 7 ++++--- stacosys/conf/config.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index 33a833e..632883d 100644 --- a/run.py +++ b/run.py @@ -39,9 +39,10 @@ def stacosys_server(config_pathname): logger.error(f"Configuration file '{config_pathname}' not found.") sys.exit(1) - # initialize config + # load config conf = Config.load(config_pathname) - logger.info(conf.__repr__()) + conf.check() + logger.info(conf) # check database file exists (prevents from creating a fresh db) db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) @@ -70,7 +71,7 @@ def stacosys_server(config_pathname): conf.get_int(ConfigParameter.SMTP_PORT), conf.get(ConfigParameter.SMTP_LOGIN), conf.get(ConfigParameter.SMTP_PASSWORD), - conf.get(ConfigParameter.SITE_ADMIN_EMAIL) + conf.get(ConfigParameter.SITE_ADMIN_EMAIL), ) # inject config parameters into flask diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 1de3ce0..8399e2e 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -74,6 +74,10 @@ class Config: assert value in ("yes", "true", "no", "false") return value in ("yes", "true") + def check(self): + for key in ConfigParameter: + assert self.get(key), f"Paramètre introuvable : {key.value}" + def __repr__(self): d = dict() for section in self._cfg.sections(): From 07bdfbf2404cad236eb1fbd1edee86342b62810e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 13 Nov 2022 09:51:29 +0100 Subject: [PATCH 458/586] Build docker image with built project --- .github/workflows/docker.yml | 13 ++++++++++++- .github/workflows/pytest.yml | 2 +- Dockerfile | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5ef1f7e..e802687 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,7 +6,18 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10.8" + - name: Install poetry + uses: abatilo/actions-poetry@v2 + with: + poetry-version: 1.2.2 + - name: Install dependencies + run: poetry install + - name: Build project + run: poetry build - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0293bc6..266d45c 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Run image + - name: Install poetry uses: abatilo/actions-poetry@v2 with: poetry-version: ${{ matrix.poetry-version }} diff --git a/Dockerfile b/Dockerfile index edddb8c..656358a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,8 @@ COPY docker/docker-init.sh /usr/local/bin/ RUN chmod +x usr/local/bin/docker-init.sh RUN cd / -#COPY ${STACOSYS_FILENAME} / -RUN wget https://github.com/kianby/stacosys/releases/download/${STACOSYS_VERSION}/${STACOSYS_FILENAME} +COPY dist/${STACOSYS_FILENAME} / +#RUN wget https://github.com/kianby/stacosys/releases/download/${STACOSYS_VERSION}/${STACOSYS_FILENAME} RUN python3 -m pip install ${STACOSYS_FILENAME} --target /stacosys RUN rm -f ${STACOSYS_FILENAME} From 6722a0de5cac7c983debbadd4c10f030b1825a04 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 13 Nov 2022 13:09:36 +0100 Subject: [PATCH 459/586] Improve config check --- run.py | 5 ++++- stacosys/conf/config.py | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index 632883d..0f7c1a3 100644 --- a/run.py +++ b/run.py @@ -41,7 +41,10 @@ def stacosys_server(config_pathname): # load config conf = Config.load(config_pathname) - conf.check() + is_config_ok, erreur_config = conf.check() + if not is_config_ok: + logger.error(f"Configuration incorrecte '{erreur_config}'") + sys.exit(1) logger.info(conf) # check database file exists (prevents from creating a fresh db) diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 8399e2e..5315036 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -71,12 +71,19 @@ class Config: def get_bool(self, key: ConfigParameter): value = self.get(key) - assert value in ("yes", "true", "no", "false") + assert value in ( + "yes", + "true", + "no", + "false", + ), f"Parameètre booléen incorrect {key.value}" return value in ("yes", "true") def check(self): for key in ConfigParameter: - assert self.get(key), f"Paramètre introuvable : {key.value}" + if not self.get(key): + return (False, key.value) + return (True, None) def __repr__(self): d = dict() From 6f3e4c0e9b804cf3d8e3b7497be163ed2cb341ef Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 13 Nov 2022 13:12:15 +0100 Subject: [PATCH 460/586] Release 3.2 --- Dockerfile | 2 +- pyproject.toml | 2 +- stacosys/__init__.py | 2 +- tests/test_stacosys.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 656358a..bea6e4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-alpine -ARG STACOSYS_VERSION=3.1 +ARG STACOSYS_VERSION=3.2 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget diff --git a/pyproject.toml b/pyproject.toml index 889c98d..f5e7d61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "stacosys" -version = "3.1" +version = "3.2" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" diff --git a/stacosys/__init__.py b/stacosys/__init__.py index f2dc0e4..080e846 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = "2.0" +__version__ = "3.2" diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 5625c8d..060ca34 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -5,4 +5,4 @@ from stacosys import __version__ class StacosysTestCase(unittest.TestCase): def test_version(self): - self.assertEqual("2.0", __version__) + self.assertEqual("3.2", __version__) From f0fe289f1a0498605991406647f78d25eca0458c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:21:35 +0100 Subject: [PATCH 461/586] Python 3.11 --- Dockerfile | 4 ++-- poetry.lock | 43 +++++++++++++++++++++--------------------- pyproject.toml | 4 ++-- stacosys/__init__.py | 2 +- tests/test_stacosys.py | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index bea6e4f..f985ffc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM python:3.10-alpine +FROM python:3.11.0-alpine -ARG STACOSYS_VERSION=3.2 +ARG STACOSYS_VERSION=3.3 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget diff --git a/poetry.lock b/poetry.lock index 2046450..010c9fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -68,7 +68,6 @@ click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -184,7 +183,7 @@ pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "flake8-black" -version = "0.3.3" +version = "0.3.4" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false @@ -192,9 +191,12 @@ python-versions = ">=3.7" [package.dependencies] black = ">=22.1.0" -flake8 = ">=3.0.0" +flake8 = ">=3" tomli = "*" +[package.extras] +develop = ["build", "twine"] + [[package]] name = "flask" version = "2.2.2" @@ -317,7 +319,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" -version = "0.10.1" +version = "0.10.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -333,7 +335,7 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.5.3" +version = "2.5.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false @@ -505,13 +507,13 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" [[package]] name = "tox" -version = "3.27.0" +version = "3.27.1" description = "tox is a generic virtualenv management and test command line tool" category = "main" optional = false @@ -524,7 +526,6 @@ packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" -tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] @@ -578,7 +579,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.16.6" +version = "20.16.7" description = "Virtual Python Environment builder" category = "main" optional = false @@ -609,8 +610,8 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" -python-versions = "~3.10" -content-hash = "73950b6c92d30141cf0ce175dce5cf45d2354c3c013c692e6cc1587ec2d3be1d" +python-versions = "~3.11" +content-hash = "ae1b03e2363b31597bef4cbb441218e1750e9747115e61dd91898400973771c6" [metadata.files] apscheduler = [ @@ -741,8 +742,8 @@ flake8 = [ {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] flake8-black = [ - {file = "flake8-black-0.3.3.tar.gz", hash = "sha256:8211f5e20e954cb57c709acccf2f3281ce27016d4c4b989c3e51f878bb7ce12a"}, - {file = "flake8_black-0.3.3-py3-none-any.whl", hash = "sha256:7d667d0059fd1aa468de1669d77cc934b7f1feeac258d57bdae69a8e73c4cd90"}, + {file = "flake8-black-0.3.4.tar.gz", hash = "sha256:7f96a4c80a828d09f1d550724e16aabb2adacd6a5f8e0bb051df422fc63d2183"}, + {file = "flake8_black-0.3.4-py3-none-any.whl", hash = "sha256:fb52f258dfa6a25645c4ba8730eadc5f2ecd32057bf6c9fc21aef1cba9fefd74"}, ] flask = [ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, @@ -848,15 +849,15 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ - {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, - {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, ] peewee = [ {file = "peewee-3.15.4.tar.gz", hash = "sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"}, ] platformdirs = [ - {file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"}, - {file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"}, + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -918,8 +919,8 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tox = [ - {file = "tox-3.27.0-py2.py3-none-any.whl", hash = "sha256:89e4bc6df3854e9fc5582462e328dd3660d7d865ba625ae5881bbc63836a6324"}, - {file = "tox-3.27.0.tar.gz", hash = "sha256:d2c945f02a03d4501374a3d5430877380deb69b218b1df9b7f1d2f2a10befaf9"}, + {file = "tox-3.27.1-py2.py3-none-any.whl", hash = "sha256:f52ca66eae115fcfef0e77ef81fd107133d295c97c52df337adedb8dfac6ab84"}, + {file = "tox-3.27.1.tar.gz", hash = "sha256:b2a920e35a668cc06942ffd1cf3a4fb221a4d909ca72191fb6d84b0b18a7be04"}, ] typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, @@ -938,8 +939,8 @@ urllib3 = [ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] virtualenv = [ - {file = "virtualenv-20.16.6-py3-none-any.whl", hash = "sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108"}, - {file = "virtualenv-20.16.6.tar.gz", hash = "sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e"}, + {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, + {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, ] werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, diff --git a/pyproject.toml b/pyproject.toml index f5e7d61..d8d8746 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [tool.poetry] name = "stacosys" -version = "3.2" +version = "3.3" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" include = ["run.py"] [tool.poetry.dependencies] -python = "~3.10" +python = "~3.11" apscheduler = "^3.6.3" pyrss2gen = "^1.1" markdown = "^3.1.1" diff --git a/stacosys/__init__.py b/stacosys/__init__.py index 080e846..587c2ab 100644 --- a/stacosys/__init__.py +++ b/stacosys/__init__.py @@ -1 +1 @@ -__version__ = "3.2" +__version__ = "3.3" diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py index 060ca34..55e0869 100644 --- a/tests/test_stacosys.py +++ b/tests/test_stacosys.py @@ -5,4 +5,4 @@ from stacosys import __version__ class StacosysTestCase(unittest.TestCase): def test_version(self): - self.assertEqual("3.2", __version__) + self.assertEqual("3.3", __version__) From 86f0847514f0a03b89afd7cca3df471c122dc648 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:23:23 +0100 Subject: [PATCH 462/586] CI Python 3.11.0 --- .github/workflows/docker.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pytest.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e802687..bf4f989 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10.8" + python-version: "3.11.0" - name: Install poetry uses: abatilo/actions-poetry@v2 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6b63472..fcb2689 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10.8" + python-version: "3.11.0" - name: Install poetry uses: abatilo/actions-poetry@v2 with: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 266d45c..81d2900 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.10.8] + python-version: [3.11.0] poetry-version: [1.2.2] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} From 9000606581ed6367fba57d2820fb0a1b0cfe2a8d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 18 Nov 2022 21:02:35 +0100 Subject: [PATCH 463/586] black format --- stacosys/core/rss.py | 13 +++++++++++-- stacosys/db/dao.py | 4 +++- stacosys/interface/web/admin.py | 4 +++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index 958f155..ce5253a 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -34,11 +34,20 @@ class Rss: .order_by(-Comment.published) .limit(10) ): - item_link = "%s://%s%s" % (self._rss_proto, self._site_url, row.url) + item_link = "%s://%s%s" % ( + self._rss_proto, + self._site_url, + row.url, + ) items.append( PyRSS2Gen.RSSItem( title="%s - %s://%s%s" - % (self._rss_proto, row.author_name, self._site_url, row.url), + % ( + self._rss_proto, + row.author_name, + self._site_url, + row.url, + ), link=item_link, description=md.convert(row.content), guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 597579d..f2876d2 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -47,7 +47,9 @@ def count_published_comments(url): .where((Comment.url == url) & (Comment.published.is_null(False))) .count() if url - else Comment.select(Comment).where(Comment.published.is_null(False)).count() + else Comment.select(Comment) + .where(Comment.published.is_null(False)) + .count() ) diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index c65ee0a..735d457 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -51,7 +51,9 @@ def logout(): @app.route("/web/admin", methods=["GET"]) def admin_homepage(): - if not ("user" in session and session["user"] == app.config.get("WEB_USERNAME")): + if not ( + "user" in session and session["user"] == app.config.get("WEB_USERNAME") + ): # TODO localization flash("Vous avez été déconnecté.") return redirect("/web/login") From c6ca525778d7dfdf38d6540897d077a954b3bdbf Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 14:44:06 +0100 Subject: [PATCH 464/586] move run.py --- run.sh | 2 +- run.py => stacosys/run.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename run.py => stacosys/run.py (100%) diff --git a/run.sh b/run.sh index 3ee772c..fb544ec 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -python3 run.py "$@" +python3 stacosys/run.py "$@" diff --git a/run.py b/stacosys/run.py similarity index 100% rename from run.py rename to stacosys/run.py From ceed951796b15c5615f5460b6f637ca50ebcded9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:40:15 +0100 Subject: [PATCH 465/586] check everything with a Makefile --- Makefile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..02911e8 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: test typehint lint black + +test: + pytest + +typehint: + mypy --ignore-missing-imports stacosys/ + +lint: + pylint stacosys/ + +black: + black -l 79 stacosys/ \ No newline at end of file From 9d10bce918707a63576f9a39ec76393d99beef24 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:40:58 +0100 Subject: [PATCH 466/586] Upgrade dev tools --- poetry.lock | 284 ++++++++++++++++++++++++++++++++++++++++--------- pyproject.toml | 14 +-- 2 files changed, 241 insertions(+), 57 deletions(-) diff --git a/poetry.lock b/poetry.lock index 010c9fa..5aa408f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,12 +26,16 @@ twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." +name = "astroid" +version = "2.12.12" +description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} [[package]] name = "attrs" @@ -121,6 +125,9 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["toml"] @@ -140,6 +147,17 @@ requests = ">=1.0.0" [package.extras] yaml = ["PyYAML (>=3.10)"] +[[package]] +name = "dill" +version = "0.3.6" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "distlib" version = "0.3.6" @@ -231,6 +249,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + [[package]] name = "itsdangerous" version = "2.1.2" @@ -253,6 +285,14 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "lazy-object-proxy" +version = "1.8.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "markdown" version = "3.4.1" @@ -282,19 +322,19 @@ python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.942" +version = "0.991" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] mypy-extensions = ">=0.4.3" -tomli = ">=1.1.0" typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] @@ -381,6 +421,27 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "pylint" +version = "2.15.6" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.7.2" + +[package.dependencies] +astroid = ">=2.12.12,<=2.14.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = ">=0.2" +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pyparsing" version = "3.0.9" @@ -402,37 +463,33 @@ python-versions = "*" [[package]] name = "pytest" -version = "6.2.5" +version = "7.2.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "4.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] @@ -476,7 +533,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "65.5.1" +version = "65.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -511,6 +568,14 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tox" version = "3.27.1" @@ -608,18 +673,27 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + [metadata] lock-version = "1.1" python-versions = "~3.11" -content-hash = "ae1b03e2363b31597bef4cbb441218e1750e9747115e61dd91898400973771c6" +content-hash = "d26f06a75123eb12e0ad3a77ffa15ead9cd93fd4c7f1ae10ab7d841910805b61" [metadata.files] apscheduler = [ {file = "APScheduler-3.9.1.post1-py2.py3-none-any.whl", hash = "sha256:c8c618241dbb2785ed5a687504b14cb1851d6f7b5a4edf3a51e39cc6a069967a"}, {file = "APScheduler-3.9.1.post1.tar.gz", hash = "sha256:b2bea0309569da53a7261bfa0ce19c67ddbfe151bda776a6a907579fdbd3eb2a"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +astroid = [ + {file = "astroid-2.12.12-py3-none-any.whl", hash = "sha256:72702205200b2a638358369d90c222d74ebc376787af8fb2f7f2a86f7b5cc85f"}, + {file = "astroid-2.12.12.tar.gz", hash = "sha256:1c00a14f5a3ed0339d38d2e2e5b74ea2591df5861c0936bb292b84ccf3a78d83"}, ] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, @@ -726,6 +800,10 @@ coveralls = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] +dill = [ + {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, + {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, +] distlib = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, @@ -757,6 +835,10 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] itsdangerous = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, @@ -765,6 +847,27 @@ jinja2 = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"}, + {file = "lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"}, + {file = "lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"}, + {file = "lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"}, + {file = "lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"}, + {file = "lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"}, + {file = "lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"}, + {file = "lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"}, + {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"}, + {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"}, + {file = "lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"}, + {file = "lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"}, + {file = "lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"}, + {file = "lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"}, + {file = "lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"}, + {file = "lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"}, + {file = "lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"}, + {file = "lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"}, + {file = "lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"}, +] markdown = [ {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"}, @@ -816,29 +919,36 @@ mccabe = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] mypy = [ - {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, - {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, - {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, - {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, - {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, - {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, - {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, - {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, - {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, - {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, - {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, - {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, - {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, - {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, - {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, - {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, - {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, - {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, - {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, + {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, + {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, + {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, + {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, + {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, + {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, + {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, + {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, + {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, + {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, + {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, + {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, + {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, + {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, + {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, + {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, + {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, + {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, + {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, + {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, + {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, + {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, + {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, + {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -875,6 +985,10 @@ pyflakes = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] +pylint = [ + {file = "pylint-2.15.6-py3-none-any.whl", hash = "sha256:15060cc22ed6830a4049cf40bc24977744df2e554d38da1b2657591de5bcd052"}, + {file = "pylint-2.15.6.tar.gz", hash = "sha256:25b13ddcf5af7d112cf96935e21806c1da60e676f952efb650130f2a4483421c"}, +] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, @@ -883,12 +997,12 @@ pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] pytz = [ {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, @@ -903,8 +1017,8 @@ requests = [ {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] setuptools = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, + {file = "setuptools-65.6.0-py3-none-any.whl", hash = "sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840"}, + {file = "setuptools-65.6.0.tar.gz", hash = "sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -918,6 +1032,10 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +tomlkit = [ + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +] tox = [ {file = "tox-3.27.1-py2.py3-none-any.whl", hash = "sha256:f52ca66eae115fcfef0e77ef81fd107133d295c97c52df337adedb8dfac6ab84"}, {file = "tox-3.27.1.tar.gz", hash = "sha256:b2a920e35a668cc06942ffd1cf3a4fb221a4d909ca72191fb6d84b0b18a7be04"}, @@ -946,3 +1064,69 @@ werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] diff --git a/pyproject.toml b/pyproject.toml index d8d8746..d129270 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,13 +18,13 @@ tox = "^3.24.5" background = "^0.2.1" Flask = "^2.1.1" -[tool.poetry.dev-dependencies] -mypy = "^0.942" -flake8-black = "^0.3.2" -black = "^22.3.0" -pytest = "^6.2.4" -pytest-cov = "^2.12.1" -coveralls = "^3.2.0" +[tool.poetry.group.dev.dependencies] +pylint = "^2.15.5" +mypy = "^0.991" +pytest = "^7.2.0" +coveralls = "^3.3.1" +flake8-black = "^0.3.4" +pytest-cov = "^4.0.0" [build-system] requires = ["poetry-core>=1.0.0"] From 66180e8178a5807e3f170dcd2ab449fa9a698778 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:41:14 +0100 Subject: [PATCH 467/586] remove useless code --- stacosys/__init__.py | 1 - tests/test_stacosys.py | 8 -------- 2 files changed, 9 deletions(-) delete mode 100644 stacosys/__init__.py delete mode 100644 tests/test_stacosys.py diff --git a/stacosys/__init__.py b/stacosys/__init__.py deleted file mode 100644 index 587c2ab..0000000 --- a/stacosys/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "3.3" diff --git a/tests/test_stacosys.py b/tests/test_stacosys.py deleted file mode 100644 index 55e0869..0000000 --- a/tests/test_stacosys.py +++ /dev/null @@ -1,8 +0,0 @@ -import unittest - -from stacosys import __version__ - - -class StacosysTestCase(unittest.TestCase): - def test_version(self): - self.assertEqual("3.3", __version__) From b9947afff545d57b2eb0dcb0e39fd004c0b11a95 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:41:33 +0100 Subject: [PATCH 468/586] tired of vs code issues with py projects --- stacosys.code-workspace | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 stacosys.code-workspace diff --git a/stacosys.code-workspace b/stacosys.code-workspace deleted file mode 100644 index 876a149..0000000 --- a/stacosys.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file From 847ee2930a5716072d4505c5ce0c0c266cdf4e7d Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:42:08 +0100 Subject: [PATCH 469/586] Sublime text project with LSP --- stacosys.sublime-project | 21 +++ stacosys.sublime-workspace | 342 +++++++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 stacosys.sublime-project create mode 100644 stacosys.sublime-workspace diff --git a/stacosys.sublime-project b/stacosys.sublime-project new file mode 100644 index 0000000..9615733 --- /dev/null +++ b/stacosys.sublime-project @@ -0,0 +1,21 @@ +{ + "folders": + [ + { + "path": "." + } + ], + "settings": + { + "LSP": + { + "LSP-pylsp": + { + "settings": + { + "pylsp.plugins.jedi.environment": "./.venv" + } + } + } + } +} diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace new file mode 100644 index 0000000..91363df --- /dev/null +++ b/stacosys.sublime-workspace @@ -0,0 +1,342 @@ +{ + "auto_complete": + { + "selected_items": + [ + ] + }, + "buffers": + [ + { + "file": "run.sh", + "settings": + { + "buffer_size": 40, + "encoding": "UTF-8", + "line_ending": "Unix" + } + }, + { + "contents": "Package Control Messages\n========================\n\nNord\n----\n\n

    \n\n

    \n\n

    \n\n

    \n\n

    \n\n

    An arctic, north-bluish clean and elegant Sublime Text color theme.

    \n\n

    Designed for a fluent and clear workflow based on the Nord color palette.

    \n\n

    \n\n Supports many bundled languages and third-party syntax plugins.\n\n ## Getting Started\n\n Visit the [official website][nord-home] to learn all about the [syntax highlighting][nord-home#syntax] features, details of the [editor elements][nord-home#editor-details] and the [one-command setup][nord-home#setup].\n\n Learn about the [installation and activation][nord-docs-home-install] and how to [develop][nord-docs-home-develop] the theme from the [official documentations][nord-docs-home].\n\n ### Quick Start\n\n Thanks to the [_package control_ registry][pc-pkg-nord], _Nord Sublime Text_ can be installed with one command.\n Please follow the [official installation instructions][pc-install] to set up _package control_ itself in order to install available packages from the registry.\n\n > **Nord requires a minimum Sublime Text version of [3.1.0 Build 3170][sbt-blog-announce-v3.1]!**\n > Nord makes use of the new [`.sublime-color-scheme` JSON file format][sbt-docs-color_schemes] that has been introduced in Sublime Text [version 3.1.0 Build 3170][sbt-blog-announce-v3.1] and is therefore the minimum required and supported version. The `.tmTheme` XML file format][sbt-docs-tmtheme] has been officially deprecated by the Sublime Text team and is not supported by Nord anymore.\n\n

    Open the _package installation_ view through the command palette via Ctrl/+Shift+p and run the „Package Control: Install Package“ command.

    \n

    \n\n

    Search for „Nord“ and press _Enter_ to install the theme.

    \n

    \n\n See _Nord Sublime Text_'s documentation for details about more installation options like a [manual import through a local `.sublime-package` file][nord-docs-home-install#manual].\n\n #### Activation\n\n To activate the _Nord_ color theme, open the [command palette][sbt-udocs-cmdp] via Ctrl/+Shift+p and run the „UI: Select Color Theme“ command or use the _Preferences_ ➜ _Color Theme…_ menu.\n\n

    \n\n See _Nord Sublime Text_'s documentation for [more details about the activation][nord-docs-home-install#activation].\n\n ## Features\n\n

    Your IDE. Your style.

    The unified UI and editor syntax element design provides a clutter-free and fluidly merging appearance.

    \n\n

    \n\n

    Beautiful code to keep focused.

    The editor color scheme supports a wide range of programming languages — From bundled definitions up to many popular third-party syntax packages.

    \n\n

    \n\n

    Small details with unobtrusive styles.

    Popular and common code editor features like search result marker and brace matching are designed to get out of your way with a visually attractive appearance.

    \n\n

    \n\n ## Contributing\n\n Nord is an open source project and we love to receive contributions from the [community][nord-comm]!\n\n There are many ways to contribute, from [writing- and improving documentation and tutorials][nord-contrib-guide-docs], [reporting bugs][nord-contrib-guide-bugs], [submitting enhancement suggestions][nord-contrib-guide-enhance] that can be added to Nord by [submitting pull requests][nord-contrib-guide-pr].\n\n Please take a moment to read Nord's full [contributing guide][nord-contrib-guide] to learn about the development process, the project's used [styleguides][nord-contrib-guide-styles], [branch organization][nord-contrib-guide-branching] and [versioning][nord-contrib-guide-versioning] model.\n\n The guide also includes information about [minimal, complete, and verifiable examples][nord-contrib-guide-mcve] and other ways to contribute to the project like [improving existing issues][nord-contrib-guide-impr-issues] and [giving feedback on issues and pull requests][nord-contrib-guide-feedback].\n\n

    \n\n

    Copyright © 2016-present Arctic Ice Studio and Sven Greb

    \n\n

    \n\n [nord-comm]: https://www.nordtheme.com/community\n [nord-contrib-guide-branching]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#branch-organization\n [nord-contrib-guide-bugs]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#bug-reports\n [nord-contrib-guide-docs]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#documentations\n [nord-contrib-guide-enhance]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#enhancement-suggestions\n [nord-contrib-guide-feedback]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#give-feedback-on-issues-and-pull-requests\n [nord-contrib-guide-impr-issues]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#improve-issues\n [nord-contrib-guide-mcve]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#mcve\n [nord-contrib-guide-pr]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#pull-requests\n [nord-contrib-guide-styles]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#styleguides\n [nord-contrib-guide-versioning]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#versioning\n [nord-contrib-guide]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md\n [nord-docs-home-develop]: https://www.nordtheme.com/docs/ports/sublime-text/development\n [nord-docs-home-install]: https://www.nordtheme.com/docs/ports/sublime-text/installation\n [nord-docs-home-install#activation]: https://www.nordtheme.com/docs/ports/sublime-text/installation#activation\n [nord-docs-home-install#manual]: https://www.nordtheme.com/docs/ports/sublime-text/installation#manual\n [nord-docs-home]: https://www.nordtheme.com/docs/ports/sublime-text\n [nord-home]: https://www.nordtheme.com/ports/sublime-text\n [nord-home#editor-details]: https://www.nordtheme.com/ports/sublime-text#editor-details\n [nord-home#setup]: https://www.nordtheme.com/ports/sublime-text#setup\n [nord-home#syntax]: https://www.nordtheme.com/ports/sublime-text#syntax\n [pc-install]: https://packagecontrol.io/installation\n [pc-pkg-nord]: https://packagecontrol.io/packages/Nord\n [sbt-blog-announce-v3.1]: https://www.sublimetext.com/blog/articles/sublime-text-3-point-1\n [sbt-docs-color_schemes]: https://www.sublimetext.com/docs/3/color_schemes.html\n [sbt-docs-tmtheme]: https://www.sublimetext.com/docs/3/color_schemes_tmtheme.html\n [sbt-udocs-cmdp]: http://docs.sublimetext.info/en/latest/extensibility/command_palette.html", + "settings": + { + "buffer_size": 12298, + "line_ending": "Unix", + "name": "Package Control Messages", + "read_only": true, + "scratch": true + } + }, + { + "file": "stacosys/run.py", + "settings": + { + "buffer_size": 3315, + "encoding": "UTF-8", + "line_ending": "Unix" + } + } + ], + "build_system": "", + "build_system_choices": + [ + ], + "build_varint": "", + "command_palette": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + [ + "install", + "Package Control: Install Package" + ], + [ + "package re", + "Package Control: Remove Package" + ], + [ + "paka", + "Package Control: Remove Package" + ], + [ + "remove", + "Package Control: Remove Package" + ], + [ + "insta", + "Package Control: Install Package" + ], + [ + "format", + "LSP: Format Document" + ], + [ + "lsp", + "LSP: Toggle Diagnostics Panel" + ], + [ + "inst", + "Package Control: Install Package" + ], + [ + "pac", + "Package Control: Remove Package" + ] + ], + "width": 0.0 + }, + "console": + { + "height": 164.0, + "history": + [ + ] + }, + "distraction_free": + { + "menu_visible": true, + "show_minimap": false, + "show_open_files": false, + "show_tabs": false, + "side_bar_visible": false, + "status_bar_visible": false + }, + "expanded_folders": + [ + "/home/yannic/work/stacosys", + "/home/yannic/work/stacosys/stacosys", + "/home/yannic/work/stacosys/stacosys/conf", + "/home/yannic/work/stacosys/stacosys/core" + ], + "file_history": + [ + "/home/yannic/work/stacosys/stacosys/__init__.py", + "/home/yannic/work/stacosys/stacosys.sublime-project", + "/home/yannic/work/stacosys/stacosys/core/rss.py" + ], + "find": + { + "height": 0.0 + }, + "find_in_files": + { + "height": 0.0, + "where_history": + [ + ] + }, + "find_state": + { + "case_sensitive": false, + "find_history": + [ + ], + "highlight": true, + "in_selection": false, + "preserve_case": false, + "regex": false, + "replace_history": + [ + ], + "reverse": false, + "show_context": true, + "use_buffer2": true, + "whole_word": false, + "wrap": true + }, + "groups": + [ + { + "selected": 2, + "sheets": + [ + { + "buffer": 0, + "file": "run.sh", + "semi_transient": false, + "settings": + { + "buffer_size": 40, + "regions": + { + }, + "selection": + [ + [ + 27, + 27 + ] + ], + "settings": + { + "syntax": "Packages/ShellScript/Bash.sublime-syntax" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 1, + "type": "text" + }, + { + "buffer": 1, + "semi_transient": false, + "settings": + { + "buffer_size": 12298, + "regions": + { + }, + "selection": + [ + [ + 12298, + 12298 + ] + ], + "settings": + { + "auto_indent": false, + "syntax": "Packages/Text/Plain text.tmLanguage", + "tab_width": 2, + "word_wrap": true + }, + "translation.x": 0.0, + "translation.y": 2862.0, + "zoom_level": 1.0 + }, + "stack_index": 2, + "type": "text" + }, + { + "buffer": 2, + "file": "stacosys/run.py", + "semi_transient": false, + "settings": + { + "buffer_size": 3315, + "regions": + { + }, + "selection": + [ + [ + 295, + 295 + ] + ], + "settings": + { + "lsp_active": true, + "lsp_language": + { + "lsp-pylsp": "python" + }, + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 0, + "type": "text" + } + ] + } + ], + "incremental_find": + { + "height": 0.0 + }, + "input": + { + "height": 0.0 + }, + "layout": + { + "cells": + [ + [ + 0, + 0, + 1, + 1 + ] + ], + "cols": + [ + 0.0, + 1.0 + ], + "rows": + [ + 0.0, + 1.0 + ] + }, + "menu_visible": true, + "output.diagnostics": + { + "height": 132.0 + }, + "output.find_results": + { + "height": 0.0 + }, + "output.mdpopups": + { + "height": 0.0 + }, + "pinned_build_system": "", + "project": "stacosys.sublime-project", + "replace": + { + "height": 0.0 + }, + "save_all_on_build": true, + "select_file": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + ], + "width": 0.0 + }, + "select_project": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + ], + "width": 0.0 + }, + "select_symbol": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + ], + "width": 0.0 + }, + "selected_group": 0, + "settings": + { + }, + "show_minimap": true, + "show_open_files": false, + "show_tabs": true, + "side_bar_visible": true, + "side_bar_width": 308.0, + "status_bar_visible": true, + "template_settings": + { + } +} From 7b7103d38b7ae4e631d6cbc8477df997ed2a0c06 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:42:33 +0100 Subject: [PATCH 470/586] Formatting --- stacosys/run.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stacosys/run.py b/stacosys/run.py index 0f7c1a3..df838bb 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -22,7 +22,8 @@ def configure_logging(level): root_logger.setLevel(level) ch = logging.StreamHandler() ch.setLevel(level) - formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + formatter = logging.Formatter( + "[%(asctime)s] %(name)s %(levelname)s %(message)s") ch.setFormatter(formatter) root_logger.addHandler(ch) From c5da5b9be9b630b8c7337c8c4a2286264d9dcdd7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:12:47 +0100 Subject: [PATCH 471/586] fix mypy errors --- poetry.lock | 14 +++++++++++++- pyproject.toml | 1 + stacosys/__init__.py | 0 stacosys/conf/config.py | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 stacosys/__init__.py diff --git a/poetry.lock b/poetry.lock index 5aa408f..2842c27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -597,6 +597,14 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] +[[package]] +name = "types-markdown" +version = "3.4.2.1" +description = "Typing stubs for Markdown" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "4.4.0" @@ -684,7 +692,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "~3.11" -content-hash = "d26f06a75123eb12e0ad3a77ffa15ead9cd93fd4c7f1ae10ab7d841910805b61" +content-hash = "94614b000f5848489ed0e32caf25a06df9daa05bc063d0e2fe03a00f6859d27b" [metadata.files] apscheduler = [ @@ -1040,6 +1048,10 @@ tox = [ {file = "tox-3.27.1-py2.py3-none-any.whl", hash = "sha256:f52ca66eae115fcfef0e77ef81fd107133d295c97c52df337adedb8dfac6ab84"}, {file = "tox-3.27.1.tar.gz", hash = "sha256:b2a920e35a668cc06942ffd1cf3a4fb221a4d909ca72191fb6d84b0b18a7be04"}, ] +types-markdown = [ + {file = "types-Markdown-3.4.2.1.tar.gz", hash = "sha256:03c0904cf5886a7d8193e2f50bcf842afc89e0ab80f060f389f6c2635c65628f"}, + {file = "types_Markdown-3.4.2.1-py3-none-any.whl", hash = "sha256:b2333f6f4b8f69af83de359e10a097e4a3f14bbd6d2484e1829d9b0ec56fa0cb"}, +] typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, diff --git a/pyproject.toml b/pyproject.toml index d129270..d1ec3da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ peewee = "^3.14.8" tox = "^3.24.5" background = "^0.2.1" Flask = "^2.1.1" +types-markdown = "^3.4.2.1" [tool.poetry.group.dev.dependencies] pylint = "^2.15.5" diff --git a/stacosys/__init__.py b/stacosys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index 5315036..c4d7ac0 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -44,7 +44,7 @@ class Config: section, param = str(key.value).split(".") if not param: param = section - section = None + section = "" return (section, param) def exists(self, key: ConfigParameter): From b90e50e3d57d6bae5a9c2baed60416f10d1c4f83 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:13:05 +0100 Subject: [PATCH 472/586] sublime project: hide useless folders and files --- stacosys.sublime-project | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stacosys.sublime-project b/stacosys.sublime-project index 9615733..d7a3a00 100644 --- a/stacosys.sublime-project +++ b/stacosys.sublime-project @@ -2,7 +2,9 @@ "folders": [ { - "path": "." + "path": ".", + "folder_exclude_patterns": ["__pycache__",".pytest_cache", ".mypy_cache"], + "file_exclude_patterns": [".coverage"] } ], "settings": From 470155345fc25114cfc0d556639de0e0bcc2829f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:18:09 +0100 Subject: [PATCH 473/586] black default setting: line length is 88 chars max --- Makefile | 2 +- stacosys/db/dao.py | 4 +--- stacosys/interface/web/admin.py | 4 +--- stacosys/run.py | 11 +++++------ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 02911e8..18d62d8 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,4 @@ lint: pylint stacosys/ black: - black -l 79 stacosys/ \ No newline at end of file + black stacosys/ \ No newline at end of file diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index f2876d2..597579d 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -47,9 +47,7 @@ def count_published_comments(url): .where((Comment.url == url) & (Comment.published.is_null(False))) .count() if url - else Comment.select(Comment) - .where(Comment.published.is_null(False)) - .count() + else Comment.select(Comment).where(Comment.published.is_null(False)).count() ) diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index 735d457..c65ee0a 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -51,9 +51,7 @@ def logout(): @app.route("/web/admin", methods=["GET"]) def admin_homepage(): - if not ( - "user" in session and session["user"] == app.config.get("WEB_USERNAME") - ): + if not ("user" in session and session["user"] == app.config.get("WEB_USERNAME")): # TODO localization flash("Vous avez été déconnecté.") return redirect("/web/login") diff --git a/stacosys/run.py b/stacosys/run.py index df838bb..60f3a58 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -20,12 +20,11 @@ from stacosys.interface.web import admin def configure_logging(level): root_logger = logging.getLogger() root_logger.setLevel(level) - ch = logging.StreamHandler() - ch.setLevel(level) - formatter = logging.Formatter( - "[%(asctime)s] %(name)s %(levelname)s %(message)s") - ch.setFormatter(formatter) - root_logger.addHandler(ch) + handler = logging.StreamHandler() + handler.setLevel(level) + formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") + handler.setFormatter(formatter) + root_logger.addHandler(handler) def stacosys_server(config_pathname): From 10935880ac8daf0e654292458006b1b0dec48327 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Nov 2022 15:57:29 +0100 Subject: [PATCH 474/586] sort imports --- Makefile | 1 + stacosys/conf/config.py | 3 +-- stacosys/core/mailer.py | 1 - stacosys/core/rss.py | 2 +- stacosys/interface/web/admin.py | 2 +- stacosys/model/comment.py | 4 +--- stacosys/run.py | 4 +--- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 18d62d8..9e081e7 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,5 @@ lint: pylint stacosys/ black: + isort --multi-line 3 --profile black stacosys/ black stacosys/ \ No newline at end of file diff --git a/stacosys/conf/config.py b/stacosys/conf/config.py index c4d7ac0..78adea8 100644 --- a/stacosys/conf/config.py +++ b/stacosys/conf/config.py @@ -1,9 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from enum import Enum - import configparser +from enum import Enum class ConfigParameter(Enum): diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index b58f75b..21f265f 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -4,7 +4,6 @@ import logging import smtplib import ssl - from email.mime.text import MIMEText logger = logging.getLogger(__name__) diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py index ce5253a..42f9b8d 100644 --- a/stacosys/core/rss.py +++ b/stacosys/core/rss.py @@ -3,8 +3,8 @@ from datetime import datetime -import PyRSS2Gen import markdown +import PyRSS2Gen from stacosys.model.comment import Comment diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index c65ee0a..dafef93 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -4,7 +4,7 @@ import hashlib import logging -from flask import request, redirect, flash, render_template, session +from flask import flash, redirect, render_template, request, session from stacosys.db import dao from stacosys.interface import app diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index 843e8b6..a6f3324 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -1,9 +1,7 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from peewee import CharField -from peewee import DateTimeField -from peewee import TextField +from peewee import CharField, DateTimeField, TextField from stacosys.db.database import BaseModel diff --git a/stacosys/run.py b/stacosys/run.py index 60f3a58..0dd69b2 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -10,9 +10,7 @@ from stacosys.conf.config import Config, ConfigParameter from stacosys.core.mailer import Mailer from stacosys.core.rss import Rss from stacosys.db import database -from stacosys.interface import api -from stacosys.interface import app -from stacosys.interface import form +from stacosys.interface import api, app, form from stacosys.interface.web import admin From e3f92dab01243697a5da87f65e3124cfc4afee21 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Nov 2022 15:57:44 +0100 Subject: [PATCH 475/586] Switch to pyright --- stacosys.sublime-project | 20 +- stacosys.sublime-workspace | 767 +++++++++++++++++++++++++++++++++---- 2 files changed, 708 insertions(+), 79 deletions(-) diff --git a/stacosys.sublime-project b/stacosys.sublime-project index d7a3a00..7d00994 100644 --- a/stacosys.sublime-project +++ b/stacosys.sublime-project @@ -7,17 +7,13 @@ "file_exclude_patterns": [".coverage"] } ], - "settings": - { - "LSP": - { - "LSP-pylsp": - { - "settings": - { - "pylsp.plugins.jedi.environment": "./.venv" - } + "settings": { + "LSP": { + "LSP-pyright": { + "settings": { + "python.analysis.diagnosticMode": "workspace" } - } - } + } + } + } } diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index 91363df..21684d5 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -3,6 +3,10 @@ { "selected_items": [ + [ + "get", + "get_int" + ] ] }, "buffers": @@ -16,22 +20,335 @@ "line_ending": "Unix" } }, - { - "contents": "Package Control Messages\n========================\n\nNord\n----\n\n

    \n\n

    \n\n

    \n\n

    \n\n

    \n\n

    An arctic, north-bluish clean and elegant Sublime Text color theme.

    \n\n

    Designed for a fluent and clear workflow based on the Nord color palette.

    \n\n

    \n\n Supports many bundled languages and third-party syntax plugins.\n\n ## Getting Started\n\n Visit the [official website][nord-home] to learn all about the [syntax highlighting][nord-home#syntax] features, details of the [editor elements][nord-home#editor-details] and the [one-command setup][nord-home#setup].\n\n Learn about the [installation and activation][nord-docs-home-install] and how to [develop][nord-docs-home-develop] the theme from the [official documentations][nord-docs-home].\n\n ### Quick Start\n\n Thanks to the [_package control_ registry][pc-pkg-nord], _Nord Sublime Text_ can be installed with one command.\n Please follow the [official installation instructions][pc-install] to set up _package control_ itself in order to install available packages from the registry.\n\n > **Nord requires a minimum Sublime Text version of [3.1.0 Build 3170][sbt-blog-announce-v3.1]!**\n > Nord makes use of the new [`.sublime-color-scheme` JSON file format][sbt-docs-color_schemes] that has been introduced in Sublime Text [version 3.1.0 Build 3170][sbt-blog-announce-v3.1] and is therefore the minimum required and supported version. The `.tmTheme` XML file format][sbt-docs-tmtheme] has been officially deprecated by the Sublime Text team and is not supported by Nord anymore.\n\n

    Open the _package installation_ view through the command palette via Ctrl/+Shift+p and run the „Package Control: Install Package“ command.

    \n

    \n\n

    Search for „Nord“ and press _Enter_ to install the theme.

    \n

    \n\n See _Nord Sublime Text_'s documentation for details about more installation options like a [manual import through a local `.sublime-package` file][nord-docs-home-install#manual].\n\n #### Activation\n\n To activate the _Nord_ color theme, open the [command palette][sbt-udocs-cmdp] via Ctrl/+Shift+p and run the „UI: Select Color Theme“ command or use the _Preferences_ ➜ _Color Theme…_ menu.\n\n

    \n\n See _Nord Sublime Text_'s documentation for [more details about the activation][nord-docs-home-install#activation].\n\n ## Features\n\n

    Your IDE. Your style.

    The unified UI and editor syntax element design provides a clutter-free and fluidly merging appearance.

    \n\n

    \n\n

    Beautiful code to keep focused.

    The editor color scheme supports a wide range of programming languages — From bundled definitions up to many popular third-party syntax packages.

    \n\n

    \n\n

    Small details with unobtrusive styles.

    Popular and common code editor features like search result marker and brace matching are designed to get out of your way with a visually attractive appearance.

    \n\n

    \n\n ## Contributing\n\n Nord is an open source project and we love to receive contributions from the [community][nord-comm]!\n\n There are many ways to contribute, from [writing- and improving documentation and tutorials][nord-contrib-guide-docs], [reporting bugs][nord-contrib-guide-bugs], [submitting enhancement suggestions][nord-contrib-guide-enhance] that can be added to Nord by [submitting pull requests][nord-contrib-guide-pr].\n\n Please take a moment to read Nord's full [contributing guide][nord-contrib-guide] to learn about the development process, the project's used [styleguides][nord-contrib-guide-styles], [branch organization][nord-contrib-guide-branching] and [versioning][nord-contrib-guide-versioning] model.\n\n The guide also includes information about [minimal, complete, and verifiable examples][nord-contrib-guide-mcve] and other ways to contribute to the project like [improving existing issues][nord-contrib-guide-impr-issues] and [giving feedback on issues and pull requests][nord-contrib-guide-feedback].\n\n

    \n\n

    Copyright © 2016-present Arctic Ice Studio and Sven Greb

    \n\n

    \n\n [nord-comm]: https://www.nordtheme.com/community\n [nord-contrib-guide-branching]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#branch-organization\n [nord-contrib-guide-bugs]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#bug-reports\n [nord-contrib-guide-docs]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#documentations\n [nord-contrib-guide-enhance]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#enhancement-suggestions\n [nord-contrib-guide-feedback]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#give-feedback-on-issues-and-pull-requests\n [nord-contrib-guide-impr-issues]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#improve-issues\n [nord-contrib-guide-mcve]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#mcve\n [nord-contrib-guide-pr]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#pull-requests\n [nord-contrib-guide-styles]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#styleguides\n [nord-contrib-guide-versioning]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md#versioning\n [nord-contrib-guide]: https://github.com/arcticicestudio/nord/blob/develop/CONTRIBUTING.md\n [nord-docs-home-develop]: https://www.nordtheme.com/docs/ports/sublime-text/development\n [nord-docs-home-install]: https://www.nordtheme.com/docs/ports/sublime-text/installation\n [nord-docs-home-install#activation]: https://www.nordtheme.com/docs/ports/sublime-text/installation#activation\n [nord-docs-home-install#manual]: https://www.nordtheme.com/docs/ports/sublime-text/installation#manual\n [nord-docs-home]: https://www.nordtheme.com/docs/ports/sublime-text\n [nord-home]: https://www.nordtheme.com/ports/sublime-text\n [nord-home#editor-details]: https://www.nordtheme.com/ports/sublime-text#editor-details\n [nord-home#setup]: https://www.nordtheme.com/ports/sublime-text#setup\n [nord-home#syntax]: https://www.nordtheme.com/ports/sublime-text#syntax\n [pc-install]: https://packagecontrol.io/installation\n [pc-pkg-nord]: https://packagecontrol.io/packages/Nord\n [sbt-blog-announce-v3.1]: https://www.sublimetext.com/blog/articles/sublime-text-3-point-1\n [sbt-docs-color_schemes]: https://www.sublimetext.com/docs/3/color_schemes.html\n [sbt-docs-tmtheme]: https://www.sublimetext.com/docs/3/color_schemes_tmtheme.html\n [sbt-udocs-cmdp]: http://docs.sublimetext.info/en/latest/extensibility/command_palette.html", - "settings": - { - "buffer_size": 12298, - "line_ending": "Unix", - "name": "Package Control Messages", - "read_only": true, - "scratch": true - } - }, { "file": "stacosys/run.py", "settings": { - "buffer_size": 3315, + "buffer_size": 3298, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 34, + 1, + "insert", + { + "characters": "no" + }, + "AgAAAOcFAAAAAAAA6AUAAAAAAAAAAAAA6AUAAAAAAADpBQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADnBQAAAAAAAOcFAAAAAAAAAAAAAAAA8L8" + ], + [ + 35, + 1, + "insert", + { + "characters": " " + }, + "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" + ], + [ + 36, + 1, + "left_delete", + null, + "AQAAAOkFAAAAAAAA6QUAAAAAAAABAAAAIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" + ], + [ + 37, + 1, + "insert", + { + "characters": "r" + }, + "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" + ], + [ + 38, + 1, + "insert", + { + "characters": " " + }, + "AQAAAOoFAAAAAAAA6wUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" + ], + [ + 39, + 1, + "paste", + null, + "AQAAAOsFAAAAAAAA9gUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrBQAAAAAAAOsFAAAAAAAAAAAAAAAA8L8" + ], + [ + 40, + 1, + "insert", + { + "characters": " or" + }, + "AwAAAPYFAAAAAAAA9wUAAAAAAAAAAAAA9wUAAAAAAAD4BQAAAAAAAAAAAAD4BQAAAAAAAPkFAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD2BQAAAAAAAPYFAAAAAAAAAAAAAAAA8L8" + ], + [ + 41, + 1, + "insert", + { + "characters": " " + }, + "AQAAAPkFAAAAAAAA+gUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD5BQAAAAAAAPkFAAAAAAAAAAAAAAAA8L8" + ], + [ + 46, + 1, + "left_delete", + null, + "AQAAAOkFAAAAAAAA6QUAAAAAAAABAAAAcg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" + ], + [ + 47, + 1, + "insert", + { + "characters": "t" + }, + "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" + ], + [ + 59, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"get_int\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"position\":{\"character\":21,\"line\":91},\"symbolLabel\":\"get_int\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/run.py\"},\"kind\":2,\"sortText\":\"09.9999.get_int\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "get_int" + }, + "AgAAAMgLAAAAAAAAyAsAAAAAAAADAAAAZ2V0yAsAAAAAAADPCwAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLCwAAAAAAAMsLAAAAAAAAAAAAAAAA8L8" + ] + ] + }, + { + "file": "Makefile", + "settings": + { + "buffer_size": 533, + "encoding": "UTF-8", + "line_ending": "Unix" + } + }, + { + "file": "stacosys/core/mailer.py", + "settings": + { + "buffer_size": 1087, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 11, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAEIEAAAAAAAAQwQAAAAAAAAAAAAAQwQAAAAAAABPBAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABCBAAAAAAAAEIEAAAAAAAAAAAAAAAA8L8" + ], + [ + 12, + 1, + "left_delete", + null, + "AQAAAEsEAAAAAAAASwQAAAAAAAAEAAAAICAgIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABPBAAAAAAAAE8EAAAAAAAAAAAAAAAA8L8" + ], + [ + 13, + 1, + "insert", + { + "characters": "else:" + }, + "BQAAAEsEAAAAAAAATAQAAAAAAAAAAAAATAQAAAAAAABNBAAAAAAAAAAAAABNBAAAAAAAAE4EAAAAAAAAAAAAAE4EAAAAAAAATwQAAAAAAAAAAAAATwQAAAAAAABQBAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLBAAAAAAAAEsEAAAAAAAAAAAAAAAA8L8" + ], + [ + 14, + 1, + "insert", + { + "characters": "\n" + }, + "AwAAAFAEAAAAAAAAUQQAAAAAAAAAAAAAUQQAAAAAAABZBAAAAAAAAAAAAABZBAAAAAAAAF0EAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABQBAAAAAAAAFAEAAAAAAAAAAAAAAAA8L8" + ], + [ + 16, + 1, + "insert", + { + "characters": "False" + }, + "BQAAAF0EAAAAAAAAXgQAAAAAAAAAAAAAXgQAAAAAAABfBAAAAAAAAAAAAABfBAAAAAAAAGAEAAAAAAAAAAAAAGAEAAAAAAAAYQQAAAAAAAAAAAAAYQQAAAAAAABiBAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdBAAAAAAAAF0EAAAAAAAAAAAAAAAA8L8" + ], + [ + 44, + 1, + "insert", + { + "characters": "s" + }, + "AQAAAB4CAAAAAAAAHwIAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAeAgAAAAAAAB4CAAAAAAAAAAAAAAAA8L8" + ], + [ + 53, + 1, + "left_delete", + null, + "AQAAAEQEAAAAAAAARAQAAAAAAAA1AAAAICAgICAgICBlbHNlOgogICAgICAgICAgICBGYWxzZQogICAgICAgIHJldHVybiBGYWxzZQo", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABEBAAAAAAAAHkEAAAAAAAAAAAAAAAAAAA" + ], + [ + 56, + 1, + "right_delete", + null, + "AQAAACwEAAAAAAAALAQAAAAAAAAXAAAAICAgICAgICAgICAgcmV0dXJuIFRydWU", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAsBAAAAAAAAEMEAAAAAAAA////////738" + ], + [ + 70, + 1, + "left_delete", + null, + "AQAAAB4CAAAAAAAAHgIAAAAAAAABAAAAcw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAfAgAAAAAAAB8CAAAAAAAAAAAAAAAA8L8" + ], + [ + 80, + 1, + "reindent", + null, + "AQAAACsEAAAAAAAANwQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAArBAAAAAAAACsEAAAAAAAAAAAAAAAA8L8" + ], + [ + 81, + 1, + "left_delete", + null, + "AQAAADMEAAAAAAAAMwQAAAAAAAAEAAAAICAgIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3BAAAAAAAADcEAAAAAAAAAAAAAAAA8L8" + ], + [ + 82, + 1, + "insert", + { + "characters": "reru" + }, + "BAAAADMEAAAAAAAANAQAAAAAAAAAAAAANAQAAAAAAAA1BAAAAAAAAAAAAAA1BAAAAAAAADYEAAAAAAAAAAAAADYEAAAAAAAANwQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAzBAAAAAAAADMEAAAAAAAAAAAAAAAA8L8" + ], + [ + 83, + 2, + "left_delete", + null, + "AgAAADYEAAAAAAAANgQAAAAAAAABAAAAdTUEAAAAAAAANQQAAAAAAAABAAAAcg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3BAAAAAAAADcEAAAAAAAAAAAAAAAA8L8" + ], + [ + 84, + 1, + "insert", + { + "characters": "turn" + }, + "BAAAADUEAAAAAAAANgQAAAAAAAAAAAAANgQAAAAAAAA3BAAAAAAAAAAAAAA3BAAAAAAAADgEAAAAAAAAAAAAADgEAAAAAAAAOQQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA1BAAAAAAAADUEAAAAAAAAAAAAAAAA8L8" + ], + [ + 85, + 1, + "insert", + { + "characters": " True" + }, + "BQAAADkEAAAAAAAAOgQAAAAAAAAAAAAAOgQAAAAAAAA7BAAAAAAAAAAAAAA7BAAAAAAAADwEAAAAAAAAAAAAADwEAAAAAAAAPQQAAAAAAAAAAAAAPQQAAAAAAAA+BAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5BAAAAAAAADkEAAAAAAAAAAAAAAAA8L8" + ] + ] + }, + { + "file": "stacosys/db/dao.py", + "settings": + { + "buffer_size": 1633, + "line_ending": "Unix" + }, + "undo_stack": + [ + ] + }, + { + "file": "stacosys.sublime-project", + "settings": + { + "buffer_size": 381, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 4, + 1, + "", + null, + "AgAAAMMAAAAAAAAAZQEAAAAAAAAAAAAAZQEAAAAAAABlAQAAAAAAAPMAAAAgICAgInNldHRpbmdzIjoKICAgIHsKICAgICAgICAiTFNQIjoKICAgICAgICB7CiAgICAgICAgICAgICJMU1AtcHlsc3AiOgogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAic2V0dGluZ3MiOgogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJweWxzcC5wbHVnaW5zLmplZGkuZW52aXJvbm1lbnQiOiAiLi8udmVudiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0", + "AQAAAAAAAAABAAAAtgEAAAAAAADDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "paste", + null, + "AgAAACYBAAAAAAAAUwEAAAAAAAAAAAAAUwEAAAAAAABTAQAAAAAAABkAAAAvLyBQdXQgeW91ciBzZXR0aW5ncyBoZXJl", + "AQAAAAAAAAABAAAAJgEAAAAAAAA/AQAAAAAAAP///////+9/" + ] + ] + }, + { + "file": "stacosys/model/comment.py", + "settings": + { + "buffer_size": 471, + "line_ending": "Unix" + } + }, + { + "file": "stacosys/conf/config.py", + "settings": + { + "buffer_size": 2481, "encoding": "UTF-8", "line_ending": "Unix" } @@ -48,6 +365,50 @@ "last_filter": "", "selected_items": [ + [ + "diag", + "LSP: Goto Diagnostic in Project" + ], + [ + "lsp", + "LSP: Toggle Diagnostics Panel" + ], + [ + "Package Control: ", + "Package Control: Install Package" + ], + [ + "distr", + "View: Toggle Distraction Free" + ], + [ + "upg", + "Package Control: Upgrade Package" + ], + [ + "comment", + "Toggle Comment" + ], + [ + "form", + "LSP: Format Document" + ], + [ + "format", + "LSP: Format Document" + ], + [ + "lspc", + "LSP: Clear Diagnostics" + ], + [ + "ren", + "LSP: Rename Symbol" + ], + [ + "instal", + "Package Control: Install Package" + ], [ "install", "Package Control: Install Package" @@ -68,14 +429,6 @@ "insta", "Package Control: Install Package" ], - [ - "format", - "LSP: Format Document" - ], - [ - "lsp", - "LSP: Toggle Diagnostics Panel" - ], [ "inst", "Package Control: Install Package" @@ -108,17 +461,27 @@ "/home/yannic/work/stacosys", "/home/yannic/work/stacosys/stacosys", "/home/yannic/work/stacosys/stacosys/conf", - "/home/yannic/work/stacosys/stacosys/core" + "/home/yannic/work/stacosys/stacosys/core", + "/home/yannic/work/stacosys/stacosys/db", + "/home/yannic/work/stacosys/stacosys/interface", + "/home/yannic/work/stacosys/stacosys/interface/web", + "/home/yannic/work/stacosys/stacosys/model", + "/home/yannic/work/stacosys/tests" ], "file_history": [ - "/home/yannic/work/stacosys/stacosys/__init__.py", + "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/sys.pyi", + "/home/yannic/work/stacosys/stacosys/conf/config.py", + "/usr/lib64/python3.11/logging/__init__.py", "/home/yannic/work/stacosys/stacosys.sublime-project", + "/home/yannic/work/stacosys/flake8.ini", + "/home/yannic/work/stacosys/Makefile", + "/home/yannic/work/stacosys/stacosys/__init__.py", "/home/yannic/work/stacosys/stacosys/core/rss.py" ], "find": { - "height": 0.0 + "height": 28.0 }, "find_in_files": { @@ -132,6 +495,7 @@ "case_sensitive": false, "find_history": [ + "background" ], "highlight": true, "in_selection": false, @@ -141,15 +505,16 @@ [ ], "reverse": false, + "scrollbar_highlights": true, "show_context": true, "use_buffer2": true, + "use_gitignore": true, "whole_word": false, "wrap": true }, "groups": [ { - "selected": 2, "sheets": [ { @@ -171,69 +536,190 @@ ], "settings": { + "lsp_uri": "file:///home/yannic/work/stacosys/run.sh", "syntax": "Packages/ShellScript/Bash.sublime-syntax" }, "translation.x": 0.0, "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 1, + "stack_index": 7, + "stack_multiselect": false, "type": "text" }, { "buffer": 1, - "semi_transient": false, - "settings": - { - "buffer_size": 12298, - "regions": - { - }, - "selection": - [ - [ - 12298, - 12298 - ] - ], - "settings": - { - "auto_indent": false, - "syntax": "Packages/Text/Plain text.tmLanguage", - "tab_width": 2, - "word_wrap": true - }, - "translation.x": 0.0, - "translation.y": 2862.0, - "zoom_level": 1.0 - }, - "stack_index": 2, - "type": "text" - }, - { - "buffer": 2, "file": "stacosys/run.py", "semi_transient": false, "settings": { - "buffer_size": 3315, + "buffer_size": 3298, "regions": { }, "selection": [ [ - 295, - 295 + 1918, + 1918 ] ], "settings": { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], "lsp_active": true, - "lsp_language": - { - "lsp-pylsp": "python" - }, + "lsp_hover_provider_count": 10, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 964.0, + "zoom_level": 1.0 + }, + "stack_index": 6, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 2, + "file": "Makefile", + "semi_transient": false, + "settings": + { + "buffer_size": 533, + "regions": + { + }, + "selection": + [ + [ + 334, + 334 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", + "syntax": "Packages/Makefile/Makefile.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": false + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 1, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 3, + "file": "stacosys/core/mailer.py", + "selected": true, + "semi_transient": false, + "settings": + { + "buffer_size": 1087, + "regions": + { + }, + "selection": + [ + [ + 1086, + 1086 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 10, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/core/mailer.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 399.0, + "zoom_level": 1.0 + }, + "stack_index": 0, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 4, + "file": "stacosys/db/dao.py", + "semi_transient": true, + "settings": + { + "buffer_size": 1633, + "regions": + { + }, + "selection": + [ + [ + 524, + 524 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/dao.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, @@ -243,7 +729,142 @@ "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 0, + "stack_index": 3, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 5, + "file": "stacosys.sublime-project", + "semi_transient": false, + "settings": + { + "buffer_size": 381, + "regions": + { + }, + "selection": + [ + [ + 364, + 364 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys.sublime-project", + "syntax": "Packages/JSON/JSON.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 2, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 6, + "file": "stacosys/model/comment.py", + "semi_transient": false, + "settings": + { + "buffer_size": 471, + "regions": + { + }, + "selection": + [ + [ + 150, + 150 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 6, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/model/comment.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 4, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 7, + "file": "stacosys/conf/config.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2481, + "regions": + { + }, + "selection": + [ + [ + 0, + 0 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 6, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/conf/config.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 1539.0, + "zoom_level": 1.0 + }, + "stack_index": 5, + "stack_multiselect": false, "type": "text" } ] @@ -251,7 +872,7 @@ ], "incremental_find": { - "height": 0.0 + "height": 28.0 }, "input": { @@ -280,23 +901,35 @@ ] }, "menu_visible": true, + "output.LSP Log Panel": + { + "height": 0.0 + }, "output.diagnostics": { - "height": 132.0 + "height": 261.0 }, "output.find_results": { "height": 0.0 }, + "output.language servers": + { + "height": 132.0 + }, "output.mdpopups": { "height": 0.0 }, + "output.references": + { + "height": 208.0 + }, "pinned_build_system": "", "project": "stacosys.sublime-project", "replace": { - "height": 0.0 + "height": 52.0 }, "save_all_on_build": true, "select_file": @@ -334,7 +967,7 @@ "show_open_files": false, "show_tabs": true, "side_bar_visible": true, - "side_bar_width": 308.0, + "side_bar_width": 311.0, "status_bar_visible": true, "template_settings": { From add4348b3831d7e3d105de7b9ba7a81c535da4b5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Nov 2022 16:38:25 +0100 Subject: [PATCH 476/586] Replaced by Makefile / pylint + LSP pyright --- .github/workflows/lint.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index fcb2689..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: lint -on: push - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.0" - - name: Install poetry - uses: abatilo/actions-poetry@v2 - with: - poetry-version: 1.2.2 - - name: Install dependencies - run: poetry install - - name: Run flake8 - uses: julianwachholz/flake8-action@v2 - with: - checkName: "Python Lint" - path: . - plugins: flake8-spellcheck - config: flake8.ini - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2a96f1b368eeeb3a5bc379a2aff7fd59d9567c8e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Nov 2022 16:38:58 +0100 Subject: [PATCH 477/586] Reorder more consistently --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9e081e7..4ee41ee 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ -all: test typehint lint black +all: black test typehint lint + +black: + isort --multi-line 3 --profile black stacosys/ + black stacosys/ test: pytest @@ -9,6 +13,3 @@ typehint: lint: pylint stacosys/ -black: - isort --multi-line 3 --profile black stacosys/ - black stacosys/ \ No newline at end of file From 37308f52132918af37564a29d9314688c59b7005 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 20 Nov 2022 16:40:44 +0100 Subject: [PATCH 478/586] pylint fixes --- .pylintrc | 622 ++++++++++++++++++++++++++++++++ stacosys/core/mailer.py | 4 +- stacosys/db/dao.py | 4 +- stacosys/db/database.py | 1 + stacosys/interface/api.py | 10 +- stacosys/interface/form.py | 30 +- stacosys/interface/web/admin.py | 4 +- stacosys/run.py | 12 +- 8 files changed, 656 insertions(+), 31 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..51832a5 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,622 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\' represents the directory delimiter on Windows systems, it +# can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.11 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=BaseException, + Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + too-few-public-methods + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/stacosys/core/mailer.py b/stacosys/core/mailer.py index 21f265f..43ec706 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/core/mailer.py @@ -34,10 +34,10 @@ class Mailer: msg["From"] = sender context = ssl.create_default_context() + # TODO catch SMTP failure with smtplib.SMTP_SSL( self._smtp_host, self._smtp_port, context=context ) as server: server.login(self._smtp_login, self._smtp_password) server.send_message(msg, sender, receivers) - return True - return False + return True diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 597579d..fcd3689 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -7,8 +7,8 @@ from stacosys.model.comment import Comment TIME_FORMAT = "%Y-%m-%d %H:%M:%S" -def find_comment_by_id(id): - return Comment.get_by_id(id) +def find_comment_by_id(comment_id): + return Comment.get_by_id(comment_id) def notify_comment(comment: Comment): diff --git a/stacosys/db/database.py b/stacosys/db/database.py index 73c2591..08a7580 100644 --- a/stacosys/db/database.py +++ b/stacosys/db/database.py @@ -1,5 +1,6 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +# pylint: disable=import-outside-toplevel from peewee import Model from playhouse.db_url import SqliteDatabase diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 042f0fc..8c88a2e 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -21,18 +21,18 @@ def query_comments(): comments = [] url = request.args.get("url", "") - logger.info("retrieve comments for url %s" % url) + logger.info("retrieve comments for url %s", url) for comment in dao.find_published_comments_by_url(url): - d = { + comment_dto = { "author": comment.author_name, "content": comment.content, "avatar": comment.author_gravatar, "date": comment.published.strftime("%Y-%m-%d %H:%M:%S"), } if comment.author_site: - d["site"] = comment.author_site - logger.debug(d) - comments.append(d) + comment_dto["site"] = comment.author_site + logger.debug(comment_dto) + comments.append(comment_dto) return jsonify({"data": comments}) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 6955d44..04b52b3 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -14,12 +14,12 @@ logger = logging.getLogger(__name__) @app.route("/newcomment", methods=["POST"]) def new_form_comment(): data = request.form - logger.info("form data " + str(data)) + logger.info("form data %s", str(data)) # honeypot for spammers captcha = data.get("remarque", "") if captcha: - logger.warning("discard spam: data %s" % data) + logger.warning("discard spam: data %s", data) abort(400) url = data.get("url", "") @@ -32,10 +32,10 @@ def new_form_comment(): # anti-spam again if not url or not author_name or not message: - logger.warning("empty field: data %s" % data) + logger.warning("empty field: data %s", data) abort(400) if not check_form_data(data.to_dict()): - logger.warning("additional field: data %s" % data) + logger.warning("additional field: data %s", data) abort(400) # add a row to Comment table @@ -49,23 +49,24 @@ def new_form_comment(): return redirect(app.config.get("SITE_REDIRECT"), code=302) -def check_form_data(d): +def check_form_data(posted_comment): fields = ["url", "message", "site", "remarque", "author", "token", "email"] - filtered = dict(filter(lambda x: x[0] not in fields, d.items())) + filtered = dict(filter(lambda x: x[0] not in fields, posted_comment.items())) return not filtered @background.task def submit_new_comment(comment): + site_url = app.config.get("SITE_URL") comment_list = ( - "Web admin interface: %s/web/admin" % app.config.get("SITE_URL"), + f"Web admin interface: {site_url}/web/admin", "", - "author: %s" % comment.author_name, - "site: %s" % comment.author_site, - "date: %s" % comment.created, - "url: %s" % comment.url, + f"author: {comment.author_name}", + f"site: {comment.author_site}", + f"date: {comment.created}", + f"url: {comment.url}", "", - "%s" % comment.content, + comment.content, "", ) email_body = "\n".join(comment_list) @@ -78,9 +79,10 @@ def submit_new_comment(comment): # save notification datetime dao.notify_comment(comment) else: - logger.warning("rescheduled. send mail failure " + subject) + logger.warning("rescheduled. send mail failure %s", subject) @background.callback def submit_new_comment_callback(future): - pass + # TODO use future to log submit status + logger.debug(future) diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index dafef93..26d172f 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -40,7 +40,7 @@ def login(): flash("Identifiant ou mot de passe incorrect") return redirect("/web/login") # GET - return render_template("login_" + app.config.get("LANG") + ".html") + return render_template("login_" + app.config.get("LANG", "fr") + ".html") @app.route("/web/logout", methods=["GET"]) @@ -58,7 +58,7 @@ def admin_homepage(): comments = dao.find_not_published_comments() return render_template( - "admin_" + app.config.get("LANG") + ".html", + "admin_" + app.config.get("LANG", "fr") + ".html", comments=comments, baseurl=app.config.get("SITE_URL"), ) diff --git a/stacosys/run.py b/stacosys/run.py index 0dd69b2..197215b 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -34,21 +34,21 @@ def stacosys_server(config_pathname): # check config file exists if not os.path.isfile(config_pathname): - logger.error(f"Configuration file '{config_pathname}' not found.") + logger.error("Configuration file '%s' not found.", config_pathname) sys.exit(1) # load config conf = Config.load(config_pathname) is_config_ok, erreur_config = conf.check() if not is_config_ok: - logger.error(f"Configuration incorrecte '{erreur_config}'") + logger.error("Configuration incorrecte '%s'", erreur_config) sys.exit(1) logger.info(conf) # check database file exists (prevents from creating a fresh db) db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) - if not os.path.isfile(db_pathname): - logger.error(f"Database file '{db_pathname}' not found.") + if not db_pathname or not os.path.isfile(db_pathname): + logger.error("Database file '%s' not found.", db_pathname) sys.exit(1) # initialize database @@ -84,12 +84,12 @@ def stacosys_server(config_pathname): app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) app.config.update(MAILER=mailer) app.config.update(RSS=rss) - logger.info(f"start interfaces {api} {form} {admin}") + logger.info("start interfaces %s %s %s", api, form, admin) # start Flask app.run( host=conf.get(ConfigParameter.HTTP_HOST), - port=conf.get(ConfigParameter.HTTP_PORT), + port=conf.get_int(ConfigParameter.HTTP_PORT), debug=False, use_reloader=False, ) From b1c64d2cc8e0200b08a29662fd974ca336afd862 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 25 Nov 2022 19:57:37 +0100 Subject: [PATCH 479/586] More pythonic singleton with module. Apply pylint recommandations --- Makefile | 10 +- config.ini | 4 +- stacosys.sublime-workspace | 3923 ++++++++++++++--- stacosys/core/rss.py | 66 - stacosys/interface/form.py | 11 +- stacosys/interface/web/admin.py | 19 +- stacosys/run.py | 51 +- stacosys/service/__init__.py | 10 + .../config.py => service/configuration.py} | 32 +- stacosys/{core/mailer.py => service/mail.py} | 18 +- stacosys/service/rssfeed.py | 63 + tests/test_api.py | 1 - tests/test_config.py | 30 +- tests/test_form.py | 3 +- 14 files changed, 3557 insertions(+), 684 deletions(-) delete mode 100644 stacosys/core/rss.py create mode 100644 stacosys/service/__init__.py rename stacosys/{conf/config.py => service/configuration.py} (76%) rename stacosys/{core/mailer.py => service/mail.py} (71%) create mode 100644 stacosys/service/rssfeed.py diff --git a/Makefile b/Makefile index 4ee41ee..16fd38e 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ all: black test typehint lint black: - isort --multi-line 3 --profile black stacosys/ - black stacosys/ + poetry run isort --multi-line 3 --profile black stacosys/ + poetry run black stacosys/ test: - pytest + poetry run pytest typehint: - mypy --ignore-missing-imports stacosys/ + poetry run mypy --ignore-missing-imports stacosys/ lint: - pylint stacosys/ + poetry run pylint stacosys/ diff --git a/config.ini b/config.ini index 17e4cd3..5cd951a 100755 --- a/config.ini +++ b/config.ini @@ -6,7 +6,8 @@ db_sqlite_file = db.sqlite [site] name = "My blog" -url = http://blog.mydomain.com +proto = https +url = https://blog.mydomain.com admin_email = admin@mydomain.com redirect = /redirect @@ -15,7 +16,6 @@ host = 127.0.0.1 port = 8100 [rss] -proto = https file = comments.xml [smtp] diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index 21684d5..82cfd31 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -3,6 +3,30 @@ { "selected_items": [ + [ + "c", + "configuration" + ], + [ + "assertI", + "assertIsNot" + ], + [ + "Confi", + "ConfigParameter" + ], + [ + "S", + "SITE_URL" + ], + [ + "Config", + "ConfigParameter" + ], + [ + "s", + "SITE_REDIRECT" + ], [ "get", "get_int" @@ -12,307 +36,10 @@ "buffers": [ { - "file": "run.sh", + "file": "stacosys/interface/web/admin.py", "settings": { - "buffer_size": 40, - "encoding": "UTF-8", - "line_ending": "Unix" - } - }, - { - "file": "stacosys/run.py", - "settings": - { - "buffer_size": 3298, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 34, - 1, - "insert", - { - "characters": "no" - }, - "AgAAAOcFAAAAAAAA6AUAAAAAAAAAAAAA6AUAAAAAAADpBQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADnBQAAAAAAAOcFAAAAAAAAAAAAAAAA8L8" - ], - [ - 35, - 1, - "insert", - { - "characters": " " - }, - "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" - ], - [ - 36, - 1, - "left_delete", - null, - "AQAAAOkFAAAAAAAA6QUAAAAAAAABAAAAIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" - ], - [ - 37, - 1, - "insert", - { - "characters": "r" - }, - "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" - ], - [ - 38, - 1, - "insert", - { - "characters": " " - }, - "AQAAAOoFAAAAAAAA6wUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" - ], - [ - 39, - 1, - "paste", - null, - "AQAAAOsFAAAAAAAA9gUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrBQAAAAAAAOsFAAAAAAAAAAAAAAAA8L8" - ], - [ - 40, - 1, - "insert", - { - "characters": " or" - }, - "AwAAAPYFAAAAAAAA9wUAAAAAAAAAAAAA9wUAAAAAAAD4BQAAAAAAAAAAAAD4BQAAAAAAAPkFAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD2BQAAAAAAAPYFAAAAAAAAAAAAAAAA8L8" - ], - [ - 41, - 1, - "insert", - { - "characters": " " - }, - "AQAAAPkFAAAAAAAA+gUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD5BQAAAAAAAPkFAAAAAAAAAAAAAAAA8L8" - ], - [ - 46, - 1, - "left_delete", - null, - "AQAAAOkFAAAAAAAA6QUAAAAAAAABAAAAcg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" - ], - [ - 47, - 1, - "insert", - { - "characters": "t" - }, - "AQAAAOkFAAAAAAAA6gUAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpBQAAAAAAAOkFAAAAAAAAAAAAAAAA8L8" - ], - [ - 59, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"get_int\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"position\":{\"character\":21,\"line\":91},\"symbolLabel\":\"get_int\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/run.py\"},\"kind\":2,\"sortText\":\"09.9999.get_int\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "get_int" - }, - "AgAAAMgLAAAAAAAAyAsAAAAAAAADAAAAZ2V0yAsAAAAAAADPCwAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLCwAAAAAAAMsLAAAAAAAAAAAAAAAA8L8" - ] - ] - }, - { - "file": "Makefile", - "settings": - { - "buffer_size": 533, - "encoding": "UTF-8", - "line_ending": "Unix" - } - }, - { - "file": "stacosys/core/mailer.py", - "settings": - { - "buffer_size": 1087, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 11, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAEIEAAAAAAAAQwQAAAAAAAAAAAAAQwQAAAAAAABPBAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABCBAAAAAAAAEIEAAAAAAAAAAAAAAAA8L8" - ], - [ - 12, - 1, - "left_delete", - null, - "AQAAAEsEAAAAAAAASwQAAAAAAAAEAAAAICAgIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABPBAAAAAAAAE8EAAAAAAAAAAAAAAAA8L8" - ], - [ - 13, - 1, - "insert", - { - "characters": "else:" - }, - "BQAAAEsEAAAAAAAATAQAAAAAAAAAAAAATAQAAAAAAABNBAAAAAAAAAAAAABNBAAAAAAAAE4EAAAAAAAAAAAAAE4EAAAAAAAATwQAAAAAAAAAAAAATwQAAAAAAABQBAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLBAAAAAAAAEsEAAAAAAAAAAAAAAAA8L8" - ], - [ - 14, - 1, - "insert", - { - "characters": "\n" - }, - "AwAAAFAEAAAAAAAAUQQAAAAAAAAAAAAAUQQAAAAAAABZBAAAAAAAAAAAAABZBAAAAAAAAF0EAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABQBAAAAAAAAFAEAAAAAAAAAAAAAAAA8L8" - ], - [ - 16, - 1, - "insert", - { - "characters": "False" - }, - "BQAAAF0EAAAAAAAAXgQAAAAAAAAAAAAAXgQAAAAAAABfBAAAAAAAAAAAAABfBAAAAAAAAGAEAAAAAAAAAAAAAGAEAAAAAAAAYQQAAAAAAAAAAAAAYQQAAAAAAABiBAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdBAAAAAAAAF0EAAAAAAAAAAAAAAAA8L8" - ], - [ - 44, - 1, - "insert", - { - "characters": "s" - }, - "AQAAAB4CAAAAAAAAHwIAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAeAgAAAAAAAB4CAAAAAAAAAAAAAAAA8L8" - ], - [ - 53, - 1, - "left_delete", - null, - "AQAAAEQEAAAAAAAARAQAAAAAAAA1AAAAICAgICAgICBlbHNlOgogICAgICAgICAgICBGYWxzZQogICAgICAgIHJldHVybiBGYWxzZQo", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABEBAAAAAAAAHkEAAAAAAAAAAAAAAAAAAA" - ], - [ - 56, - 1, - "right_delete", - null, - "AQAAACwEAAAAAAAALAQAAAAAAAAXAAAAICAgICAgICAgICAgcmV0dXJuIFRydWU", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAsBAAAAAAAAEMEAAAAAAAA////////738" - ], - [ - 70, - 1, - "left_delete", - null, - "AQAAAB4CAAAAAAAAHgIAAAAAAAABAAAAcw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAfAgAAAAAAAB8CAAAAAAAAAAAAAAAA8L8" - ], - [ - 80, - 1, - "reindent", - null, - "AQAAACsEAAAAAAAANwQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAArBAAAAAAAACsEAAAAAAAAAAAAAAAA8L8" - ], - [ - 81, - 1, - "left_delete", - null, - "AQAAADMEAAAAAAAAMwQAAAAAAAAEAAAAICAgIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3BAAAAAAAADcEAAAAAAAAAAAAAAAA8L8" - ], - [ - 82, - 1, - "insert", - { - "characters": "reru" - }, - "BAAAADMEAAAAAAAANAQAAAAAAAAAAAAANAQAAAAAAAA1BAAAAAAAAAAAAAA1BAAAAAAAADYEAAAAAAAAAAAAADYEAAAAAAAANwQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAzBAAAAAAAADMEAAAAAAAAAAAAAAAA8L8" - ], - [ - 83, - 2, - "left_delete", - null, - "AgAAADYEAAAAAAAANgQAAAAAAAABAAAAdTUEAAAAAAAANQQAAAAAAAABAAAAcg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3BAAAAAAAADcEAAAAAAAAAAAAAAAA8L8" - ], - [ - 84, - 1, - "insert", - { - "characters": "turn" - }, - "BAAAADUEAAAAAAAANgQAAAAAAAAAAAAANgQAAAAAAAA3BAAAAAAAAAAAAAA3BAAAAAAAADgEAAAAAAAAAAAAADgEAAAAAAAAOQQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA1BAAAAAAAADUEAAAAAAAAAAAAAAAA8L8" - ], - [ - 85, - 1, - "insert", - { - "characters": " True" - }, - "BQAAADkEAAAAAAAAOgQAAAAAAAAAAAAAOgQAAAAAAAA7BAAAAAAAAAAAAAA7BAAAAAAAADwEAAAAAAAAAAAAADwEAAAAAAAAPQQAAAAAAAAAAAAAPQQAAAAAAAA+BAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5BAAAAAAAADkEAAAAAAAAAAAAAAAA8L8" - ] - ] - }, - { - "file": "stacosys/db/dao.py", - "settings": - { - "buffer_size": 1633, - "line_ending": "Unix" - }, - "undo_stack": - [ - ] - }, - { - "file": "stacosys.sublime-project", - "settings": - { - "buffer_size": 381, + "buffer_size": 2472, "encoding": "UTF-8", "line_ending": "Unix" }, @@ -321,37 +48,3079 @@ [ 4, 1, - "", - null, - "AgAAAMMAAAAAAAAAZQEAAAAAAAAAAAAAZQEAAAAAAABlAQAAAAAAAPMAAAAgICAgInNldHRpbmdzIjoKICAgIHsKICAgICAgICAiTFNQIjoKICAgICAgICB7CiAgICAgICAgICAgICJMU1AtcHlsc3AiOgogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAic2V0dGluZ3MiOgogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJweWxzcC5wbHVnaW5zLmplZGkuZW52aXJvbm1lbnQiOiAiLi8udmVudiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0", - "AQAAAAAAAAABAAAAtgEAAAAAAADDAAAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": "\n" + }, + "AgAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADEAQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAPC/" ], [ 8, 1, + "insert", + { + "characters": "\n" + }, + "AgAAANIAAAAAAAAA0wAAAAAAAAAAAAAAwQEAAAAAAADBAQAAAAAAAAQAAAAgICAg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0gAAAAAAAADSAAAAAAAAAAAAAAAAIINA" + ], + [ + 9, + 1, "paste", null, - "AgAAACYBAAAAAAAAUwEAAAAAAAAAAAAAUwEAAAAAAABTAQAAAAAAABkAAAAvLyBQdXQgeW91ciBzZXR0aW5ncyBoZXJl", - "AQAAAAAAAAABAAAAJgEAAAAAAAA/AQAAAAAAAP///////+9/" + "AQAAANMAAAAAAAAACwEAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wAAAAAAAADTAAAAAAAAAAAAAAAAAPC/" + ], + [ + 12, + 1, + "reindent", + null, + "AQAAAPkBAAAAAAAA/QEAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA+QEAAAAAAAD5AQAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 1, + "insert", + { + "characters": "cofn" + }, + "BAAAAP0BAAAAAAAA/gEAAAAAAAAAAAAA/gEAAAAAAAD/AQAAAAAAAAAAAAD/AQAAAAAAAAACAAAAAAAAAAAAAAACAAAAAAAAAQIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA/QEAAAAAAAD9AQAAAAAAAAAAAAAAAPC/" + ], + [ + 14, + 2, + "left_delete", + null, + "AgAAAAACAAAAAAAAAAIAAAAAAAABAAAAbv8BAAAAAAAA/wEAAAAAAAABAAAAZg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQIAAAAAAAABAgAAAAAAAAAAAAAAAPC/" + ], + [ + 15, + 1, + "insert", + { + "characters": "nfug" + }, + "BAAAAP8BAAAAAAAAAAIAAAAAAAAAAAAAAAIAAAAAAAABAgAAAAAAAAAAAAABAgAAAAAAAAICAAAAAAAAAAAAAAICAAAAAAAAAwIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA/wEAAAAAAAD/AQAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 2, + "left_delete", + null, + "AgAAAAICAAAAAAAAAgIAAAAAAAABAAAAZwECAAAAAAAAAQIAAAAAAAABAAAAdQ", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAwIAAAAAAAADAgAAAAAAAAAAAAAAAPC/" + ], + [ + 17, + 1, + "insert", + { + "characters": "ig" + }, + "AgAAAAECAAAAAAAAAgIAAAAAAAAAAAAAAgIAAAAAAAADAgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQIAAAAAAAABAgAAAAAAAAAAAAAAAPC/" + ], + [ + 18, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAAMCAAAAAAAABAIAAAAAAAAAAAAABAIAAAAAAAAFAgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAwIAAAAAAAADAgAAAAAAAAAAAAAAAPC/" + ], + [ + 19, + 1, + "insert", + { + "characters": " Config" + }, + "BwAAAAUCAAAAAAAABgIAAAAAAAAAAAAABgIAAAAAAAAHAgAAAAAAAAAAAAAHAgAAAAAAAAgCAAAAAAAAAAAAAAgCAAAAAAAACQIAAAAAAAAAAAAACQIAAAAAAAAKAgAAAAAAAAAAAAAKAgAAAAAAAAsCAAAAAAAAAAAAAAsCAAAAAAAADAIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABQIAAAAAAAAFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 20, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAAwCAAAAAAAADgIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAIAAAAAAAAMAgAAAAAAAAAAAAAAAPC/" + ], + [ + 30, + 1, + "insert", + { + "characters": " " + }, + "AQAAAGcCAAAAAAAAaAIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwIAAAAAAABnAgAAAAAAAAAAAAAAAPC/" + ], + [ + 32, + 1, + "insert", + { + "characters": "config." + }, + "BwAAAGcCAAAAAAAAaAIAAAAAAAAAAAAAaAIAAAAAAABpAgAAAAAAAAAAAABpAgAAAAAAAGoCAAAAAAAAAAAAAGoCAAAAAAAAawIAAAAAAAAAAAAAawIAAAAAAABsAgAAAAAAAAAAAABsAgAAAAAAAG0CAAAAAAAAAAAAAG0CAAAAAAAAbgIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwIAAAAAAABnAgAAAAAAAAAAAAAAAPC/" + ], + [ + 33, + 1, + "insert", + { + "characters": "get" + }, + "AwAAAG4CAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABwAgAAAAAAAAAAAABwAgAAAAAAAHECAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbgIAAAAAAABuAgAAAAAAAAAAAAAAAPC/" + ], + [ + 34, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAHECAAAAAAAAcwIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "insert", + { + "characters": "Confi" + }, + "BQAAAHICAAAAAAAAcwIAAAAAAAAAAAAAcwIAAAAAAAB0AgAAAAAAAAAAAAB0AgAAAAAAAHUCAAAAAAAAAAAAAHUCAAAAAAAAdgIAAAAAAAAAAAAAdgIAAAAAAAB3AgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcgIAAAAAAAByAgAAAAAAAAAAAAAAAPC/" + ], + [ + 37, + 1, + "insert_completion", + { + "completion": "ConfigParameter", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "ConfigParameter" + }, + "AgAAAHICAAAAAAAAcgIAAAAAAAAFAAAAQ29uZmlyAgAAAAAAAIECAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwIAAAAAAAB3AgAAAAAAAAAAAAAAAPC/" + ], + [ + 38, + 1, + "insert", + { + "characters": ".W" + }, + "AgAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACDAgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAPC/" + ], + [ + 39, + 1, + "insert_completion", + { + "completion": "WEB_USERNAME", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "WEB_USERNAME" + }, + "AgAAAIICAAAAAAAAggIAAAAAAAABAAAAV4ICAAAAAAAAjgIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgwIAAAAAAACDAgAAAAAAAAAAAAAAAPC/" + ], + [ + 43, + 1, + "right_delete", + null, + "AQAAAI8CAAAAAAAAjwIAAAAAAAAfAAAAIGFwcC5jb25maWcuZ2V0KCJXRUJfVVNFUk5BTUUiKQ", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjwIAAAAAAACuAgAAAAAAAAAAAAAAAPC/" + ], + [ + 50, + 1, + "paste", + null, + "AQAAAKgCAAAAAAAA0AIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqAIAAAAAAACoAgAAAAAAAAAAAAAAAPC/" + ], + [ + 51, + 1, + "insert", + { + "characters": " " + }, + "AQAAANACAAAAAAAA0QIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0AIAAAAAAADQAgAAAAAAAAAAAAAAAPC/" + ], + [ + 55, + 1, + "insert", + { + "characters": "PA" + }, + "AwAAAMcCAAAAAAAAyAIAAAAAAAAAAAAAyAIAAAAAAADIAgAAAAAAAAgAAABVU0VSTkFNRcgCAAAAAAAAyQIAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzwIAAAAAAADHAgAAAAAAAAAAAAAAAPC/" + ], + [ + 56, + 1, + "insert_completion", + { + "completion": "WEB_PASSWORD", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "WEB_PASSWORD" + }, + "AgAAAMMCAAAAAAAAwwIAAAAAAAAGAAAAV0VCX1BBwwIAAAAAAADPAgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAyQIAAAAAAADJAgAAAAAAAAAAAAAAAPC/" + ], + [ + 59, + 2, + "right_delete", + null, + "AgAAANECAAAAAAAA0QIAAAAAAAAeAAAAYXBwLmNvbmZpZy5nZXQoIldFQl9QQVNTV09SRCIp0QIAAAAAAADRAgAAAAAAAAEAAAAg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QIAAAAAAADvAgAAAAAAAAAAAAAAAPC/" + ], + [ + 69, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAKcFAAAAAAAAqAUAAAAAAAAAAAAAqAUAAAAAAACsBQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApwUAAAAAAACnBQAAAAAAAAAAAAAAAPC/" + ], + [ + 77, + 1, + "paste", + null, + "AQAAAKwFAAAAAAAAvQUAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArAUAAAAAAACsBQAAAAAAAAAAAAAAAPC/" + ], + [ + 85, + 1, + "paste", + null, + "AgAAAPMFAAAAAAAAGwYAAAAAAAAAAAAAGwYAAAAAAAAbBgAAAAAAAB4AAABhcHAuY29uZmlnLmdldCgiV0VCX1VTRVJOQU1FIik", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wUAAAAAAAARBgAAAAAAAAAAAAAAAPC/" + ], + [ + 98, + 1, + "paste", + null, + "AQAAAOwGAAAAAAAABwcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA7AYAAAAAAADsBgAAAAAAAAAAAAAAAPC/" + ], + [ + 99, + 1, + "insert", + { + "characters": " " + }, + "AQAAAAcHAAAAAAAACAcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABwcAAAAAAAAHBwAAAAAAAAAAAAAAAPC/" + ], + [ + 101, + 1, + "left_delete", + null, + "AQAAAAYHAAAAAAAABgcAAAAAAAABAAAALg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABwcAAAAAAAAHBwAAAAAAAAAAAAAAAPC/" + ], + [ + 102, + 1, + "insert", + { + "characters": ".LA" + }, + "AwAAAAYHAAAAAAAABwcAAAAAAAAAAAAABwcAAAAAAAAIBwAAAAAAAAAAAAAIBwAAAAAAAAkHAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABgcAAAAAAAAGBwAAAAAAAAAAAAAAAPC/" + ], + [ + 103, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"label\":\"LANG\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"LANG\",\"position\":{\"character\":48,\"line\":63},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.LANG\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "LANG" + }, + "AgAAAAcHAAAAAAAABwcAAAAAAAACAAAATEEHBwAAAAAAAAsHAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACQcAAAAAAAAJBwAAAAAAAAAAAAAAAPC/" + ], + [ + 104, + 1, + "insert", + { + "characters": ")" + }, + "AQAAAAsHAAAAAAAADAcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwcAAAAAAAALBwAAAAAAAAAAAAAAAPC/" + ], + [ + 107, + 1, + "right_delete", + null, + "AQAAAAwHAAAAAAAADAcAAAAAAAAdAAAAIGFwcC5jb25maWcuZ2V0KCJMQU5HIiwgImZyIik", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAcAAAAAAAApBwAAAAAAAAAAAAAAAPC/" + ], + [ + 112, + 1, + "insert", + { + "characters": "f" + }, + "AQAAAOEGAAAAAAAA4gYAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QYAAAAAAADhBgAAAAAAAAAAAAAAAPC/" + ], + [ + 116, + 1, + "right_delete", + null, + "AQAAAOEGAAAAAAAA4QYAAAAAAAABAAAAZg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QYAAAAAAADhBgAAAAAAAAAAAAAAAPC/" + ], + [ + 121, + 1, + "insert", + { + "characters": " " + }, + "AQAAAEMHAAAAAAAARAcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwcAAAAAAABDBwAAAAAAAAAAAAAAAPC/" + ], + [ + 123, + 1, + "paste", + null, + "AQAAAEMHAAAAAAAAXgcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwcAAAAAAABDBwAAAAAAAAAAAAAAAPC/" + ], + [ + 124, + 1, + "insert", + { + "characters": "S" + }, + "AQAAAF4HAAAAAAAAXwcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXgcAAAAAAABeBwAAAAAAAAAAAAAAAPC/" + ], + [ + 128, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"label\":\"SITE_URL\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"SITE_URL\",\"position\":{\"character\":44,\"line\":65},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.SITE_URL\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "SITE_URL" + }, + "AgAAAF4HAAAAAAAAXgcAAAAAAAABAAAAU14HAAAAAAAAZgcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXwcAAAAAAABfBwAAAAAAAAAAAAAAAPC/" + ], + [ + 129, + 1, + "insert", + { + "characters": ")" + }, + "AQAAAGYHAAAAAAAAZwcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZgcAAAAAAABmBwAAAAAAAAAAAAAAAPC/" + ], + [ + 132, + 1, + "right_delete", + null, + "AQAAAGcHAAAAAAAAZwcAAAAAAAAbAAAAIGFwcC5jb25maWcuZ2V0KCJTSVRFX1VSTCIp", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwcAAAAAAACCBwAAAAAAAAAAAAAAAPC/" + ], + [ + 140, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAKUIAAAAAAAApggAAAAAAAAAAAAApggAAAAAAACuCAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQgAAAAAAAClCAAAAAAAAP///////+9/" + ], + [ + 145, + 1, + "paste", + null, + "AQAAAAwBAAAAAAAALQEAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" + ], + [ + 146, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAC0BAAAAAAAALgEAAAAAAAAAAAAAyAgAAAAAAADICAAAAAAAAAgAAAAgICAgICAgIA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALQEAAAAAAAAtAQAAAAAAAAAAAAAAAPC/" + ], + [ + 150, + 1, + "insert", + { + "characters": "\nrss" + }, + "BQAAANEHAAAAAAAA0gcAAAAAAAAAAAAA0gcAAAAAAADWBwAAAAAAAAAAAADWBwAAAAAAANcHAAAAAAAAAAAAANcHAAAAAAAA2AcAAAAAAAAAAAAA2AcAAAAAAADZBwAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QcAAAAAAADRBwAAAAAAAAAAAAAAAPC/" + ], + [ + 151, + 1, + "insert", + { + "characters": " =" + }, + "AgAAANkHAAAAAAAA2gcAAAAAAAAAAAAA2gcAAAAAAADbBwAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2QcAAAAAAADZBwAAAAAAAAAAAAAAAPC/" + ], + [ + 152, + 1, + "insert", + { + "characters": " Rss" + }, + "BAAAANsHAAAAAAAA3AcAAAAAAAAAAAAA3AcAAAAAAADdBwAAAAAAAAAAAADdBwAAAAAAAN4HAAAAAAAAAAAAAN4HAAAAAAAA3wcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2wcAAAAAAADbBwAAAAAAAAAAAAAAAPC/" + ], + [ + 153, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAN8HAAAAAAAA4QcAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA3wcAAAAAAADfBwAAAAAAAAAAAAAAAPC/" + ], + [ + 157, + 1, + "insert", + { + "characters": "rss" + }, + "BAAAAOEIAAAAAAAA4ggAAAAAAAAAAAAA4ggAAAAAAADiCAAAAAAAABUAAABhcHAuY29uZmlnLmdldCgiUlNTIiniCAAAAAAAAOMIAAAAAAAAAAAAAOMIAAAAAAAA5AgAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QgAAAAAAAD2CAAAAAAAAAAAAAAAAPC/" + ], + [ + 160, + 1, + "left_delete", + null, + "AQAAANcIAAAAAAAA1wgAAAAAAAABAAAACg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2AgAAAAAAADYCAAAAAAAAAAAAAAAAPC/" + ], + [ + 176, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAEMDAAAAAAAARAMAAAAAAAAAAAAARAMAAAAAAABIAwAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABDAwAAAAAAAAAAAAAAAPC/" + ], + [ + 177, + 1, + "paste", + null, + "AQAAAEgDAAAAAAAAWQMAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASAMAAAAAAABIAwAAAAAAAAAAAAAAAPC/" + ], + [ + 182, + 1, + "insert", + { + "characters": "conig" + }, + "BgAAAAEFAAAAAAAAAgUAAAAAAAAAAAAAAgUAAAAAAAACBQAAAAAAABwAAABhcHAuY29uZmlnLmdldCgiTEFORyIsICJmciIpAgUAAAAAAAADBQAAAAAAAAAAAAADBQAAAAAAAAQFAAAAAAAAAAAAAAQFAAAAAAAABQUAAAAAAAAAAAAABQUAAAAAAAAGBQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQUAAAAAAAAdBQAAAAAAAAAAAAAAAPC/" + ], + [ + 183, + 2, + "left_delete", + null, + "AgAAAAUFAAAAAAAABQUAAAAAAAABAAAAZwQFAAAAAAAABAUAAAAAAAABAAAAaQ", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABgUAAAAAAAAGBQAAAAAAAAAAAAAAAPC/" + ], + [ + 184, + 1, + "insert", + { + "characters": "fig.get" + }, + "BwAAAAQFAAAAAAAABQUAAAAAAAAAAAAABQUAAAAAAAAGBQAAAAAAAAAAAAAGBQAAAAAAAAcFAAAAAAAAAAAAAAcFAAAAAAAACAUAAAAAAAAAAAAACAUAAAAAAAAJBQAAAAAAAAAAAAAJBQAAAAAAAAoFAAAAAAAAAAAAAAoFAAAAAAAACwUAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABAUAAAAAAAAEBQAAAAAAAAAAAAAAAPC/" + ], + [ + 185, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAAsFAAAAAAAADQUAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwUAAAAAAAALBQAAAAAAAAAAAAAAAPC/" + ], + [ + 186, + 1, + "insert", + { + "characters": "Confi" + }, + "BQAAAAwFAAAAAAAADQUAAAAAAAAAAAAADQUAAAAAAAAOBQAAAAAAAAAAAAAOBQAAAAAAAA8FAAAAAAAAAAAAAA8FAAAAAAAAEAUAAAAAAAAAAAAAEAUAAAAAAAARBQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAUAAAAAAAAMBQAAAAAAAAAAAAAAAPC/" + ], + [ + 188, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"label\":\"ConfigParameter\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"ConfigParameter\",\"position\":{\"character\":54,\"line\":46},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"05.0003.ConfigParameter\",\"kind\":7},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "ConfigParameter" + }, + "AgAAAAwFAAAAAAAADAUAAAAAAAAFAAAAQ29uZmkMBQAAAAAAABsFAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQUAAAAAAAARBQAAAAAAAAAAAAAAAPC/" + ], + [ + 189, + 1, + "insert", + { + "characters": ".L" + }, + "AgAAABsFAAAAAAAAHAUAAAAAAAAAAAAAHAUAAAAAAAAdBQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAGwUAAAAAAAAbBQAAAAAAAAAAAAAAAPC/" + ], + [ + 190, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"label\":\"LANG\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"LANG\",\"position\":{\"character\":66,\"line\":46},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.LANG\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "LANG" + }, + "AgAAABwFAAAAAAAAHAUAAAAAAAABAAAATBwFAAAAAAAAIAUAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAHQUAAAAAAAAdBQAAAAAAAAAAAAAAAPC/" + ], + [ + 194, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADhCQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRhbwpmcm9tIHN0YWNvc3lzLmludGVyZmFjZSBpbXBvcnQgYXBwCmZyb20gc3RhY29zeXMuY29uZi5jb25maWcgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCmZyb20gc3RhY29zeXMuY29yZS5yc3MgaW1wb3J0IFJzcwoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIHJlcXVlc3QubWV0aG9kID09ICJQT1NUIjoKICAgICAgICB1c2VybmFtZSA9IHJlcXVlc3QuZm9ybS5nZXQoInVzZXJuYW1lIikKICAgICAgICBwYXNzd29yZCA9IHJlcXVlc3QuZm9ybS5nZXQoInBhc3N3b3JkIikKICAgICAgICBpZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgICAgICAgICBzZXNzaW9uWyJ1c2VyIl0gPSB1c2VybmFtZQogICAgICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiSWRlbnRpZmlhbnQgb3UgbW90IGRlIHBhc3NlIGluY29ycmVjdCIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKICAgICMgR0VUCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKCJsb2dpbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiKQoKCkBhcHAucm91dGUoIi93ZWIvbG9nb3V0IiwgbWV0aG9kcz1bIkdFVCJdKQpkZWYgbG9nb3V0KCk6CiAgICBzZXNzaW9uLnBvcCgidXNlciIpCiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBhZG1pbl9ob21lcGFnZSgpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIG5vdCAoInVzZXIiIGluIHNlc3Npb24gYW5kIHNlc3Npb25bInVzZXIiXSA9PSBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5XRUJfVVNFUk5BTUUpKToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIlZvdXMgYXZleiDDqXTDqSBkw6ljb25uZWN0w6kuIikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQoKICAgIGNvbW1lbnRzID0gZGFvLmZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKAogICAgICAgICJhZG1pbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiLAogICAgICAgIGNvbW1lbnRzPWNvbW1lbnRzLAogICAgICAgIGJhc2V1cmw9Y29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgYWRtaW5fYWN0aW9uKCk6CiAgICByc3MgPSBSc3MoKQogICAgY29tbWVudCA9IGRhby5maW5kX2NvbW1lbnRfYnlfaWQocmVxdWVzdC5mb3JtLmdldCgiY29tbWVudCIpKQogICAgaWYgY29tbWVudCBpcyBOb25lOgogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgaW50cm91dmFibGUiKQogICAgZWxpZiByZXF1ZXN0LmZvcm0uZ2V0KCJhY3Rpb24iKSA9PSAiQVBQUk9WRSI6CiAgICAgICAgZGFvLnB1Ymxpc2hfY29tbWVudChjb21tZW50KQogICAgICAgIHJzcy5nZW5lcmF0ZSgpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBwdWJsacOpIikKICAgIGVsc2U6CiAgICAgICAgZGFvLmRlbGV0ZV9jb21tZW50KGNvbW1lbnQpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBzdXBwcmltw6kiKQogICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2FkbWluIikKAAAAAAAAAADxCQAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIgUAAAAAAAAiBQAAAAAAAAAAAAAAAPC/" + ], + [ + 224, + 1, + "toggle_breakpoint", + null, + "AQAAAEQDAAAAAAAAfgMAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATAMAAAAAAABMAwAAAAAAAAAAAAAAAPC/" + ], + [ + 232, + 1, + "right_delete", + null, + "AQAAAEgDAAAAAAAASAMAAAAAAAA1AAAAaW1wb3J0IHBkYjsgcGRiLnNldF90cmFjZSgpICAjIGJyZWFrcG9pbnQgNzE4YjY2MDQgLy8", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfQMAAAAAAABIAwAAAAAAAAAAAAAAAEJA" + ], + [ + 234, + 1, + "left_delete", + null, + "AQAAAEMDAAAAAAAAQwMAAAAAAAABAAAACg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAARAMAAAAAAABEAwAAAAAAAAAAAAAAAAAA" + ], + [ + 237, + 1, + "right_delete", + null, + "AQAAAEMDAAAAAAAAQwMAAAAAAAAEAAAAICAgIA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABHAwAAAAAAAP///////+9/" + ], + [ + 256, + 1, + "toggle_breakpoint", + null, + "AQAAAEQDAAAAAAAAfgMAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwMAAAAAAABLAwAAAAAAAAAAAAAAAPC/" + ], + [ + 264, + 1, + "toggle_breakpoint", + null, + "AQAAAEQDAAAAAAAARAMAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGJlMjhiNmVmIC8vCg", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfQMAAAAAAAB9AwAAAAAAAAAAAAAAAPC/" + ], + [ + 267, + 2, + "toggle_breakpoint", + null, + "AgAAADcDAAAAAAAAbQMAAAAAAAAAAAAANwMAAAAAAAA3AwAAAAAAADYAAABpbXBvcnQgcGRiOyBwZGIuc2V0X3RyYWNlKCkgICMgYnJlYWtwb2ludCAzZDI2NjIzOCAvLwo", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABDAwAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKYAAAAAAAAApgAAAAAAAAAAAAAAAADwvw" + ], + [ + 14, + 1, + "right_delete", + null, + "AQAAAK0AAAAAAAAArQAAAAAAAAABAAAALg", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAK0AAAAAAAAArQAAAAAAAAAAAAAAAADwvw" + ], + [ + 15, + 1, + "insert", + { + "characters": " " + }, + "AQAAAK0AAAAAAAAArgAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAK0AAAAAAAAArQAAAAAAAAAAAAAAAADwvw" + ], + [ + 17, + 1, + "insert", + { + "characters": "\n//" + }, + "AwAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAC2AAAAAAAAALcAAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" + ], + [ + 19, + 1, + "insert", + { + "characters": "#" + }, + "AgAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC2AAAAAAAAAAIAAAAvLw", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtQAAAAAAAAAAAAAAAADwvw" + ], + [ + 28, + 1, + "insert", + { + "characters": "config." + }, + "BwAAADECAAAAAAAAMgIAAAAAAAAAAAAAMgIAAAAAAAAzAgAAAAAAAAAAAAAzAgAAAAAAADQCAAAAAAAAAAAAADQCAAAAAAAANQIAAAAAAAAAAAAANQIAAAAAAAA2AgAAAAAAAAAAAAA2AgAAAAAAADcCAAAAAAAAAAAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADECAAAAAAAAMQIAAAAAAAAAAAAAAADwvw" + ], + [ + 39, + 1, + "insert", + { + "characters": " " + }, + "AgAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" + ], + [ + 40, + 2, + "left_delete", + null, + "AgAAALUAAAAAAAAAtQAAAAAAAAABAAAAILQAAAAAAAAAtAAAAAAAAAABAAAAIA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAtgAAAAAAAAAAAAAAAADwvw" + ], + [ + 41, + 1, + "insert", + { + "characters": "as" + }, + "AgAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" + ], + [ + 42, + 2, + "left_delete", + null, + "AgAAALUAAAAAAAAAtQAAAAAAAAABAAAAc7QAAAAAAAAAtAAAAAAAAAABAAAAYQ", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAtgAAAAAAAAAAAAAAAADwvw" + ], + [ + 43, + 1, + "insert", + { + "characters": " as" + }, + "AwAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAC2AAAAAAAAALcAAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" + ], + [ + 44, + 1, + "insert", + { + "characters": " cfg" + }, + "BAAAALcAAAAAAAAAuAAAAAAAAAAAAAAAuAAAAAAAAAC5AAAAAAAAAAAAAAC5AAAAAAAAALoAAAAAAAAAAAAAALoAAAAAAAAAuwAAAAAAAAAAAAAA", + "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtwAAAAAAAAAAAAAAAADwvw" + ], + [ + 10, + 1, + "insert", + { + "characters": "Config" + }, + "BwAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACvAAAAAAAAAA0AAABjb25maWcgYXMgY2ZnrwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAAsgAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuAAAAAAAAALsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 17, + 2, + "right_delete", + null, + "AgAAADECAAAAAAAAMQIAAAAAAAAGAAAAY29uZmlnMQIAAAAAAAAxAgAAAAAAAAEAAAAu", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAxAgAAAAAAADcCAAAAAAAAAAAAAAAA8L8" + ], + [ + 21, + 1, + "insert", + { + "characters": "," + }, + "AQAAALQAAAAAAAAAtQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 22, + 1, + "insert", + { + "characters": " ConfigPar" + }, + "CgAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC3AAAAAAAAAAAAAAC3AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAAC9AAAAAAAAAAAAAAC9AAAAAAAAAL4AAAAAAAAAAAAAAL4AAAAAAAAAvwAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC1AAAAAAAAALUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 23, + 1, + "insert_completion", + { + "completion": "ConfigParameter", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "ConfigParameter" + }, + "AgAAALYAAAAAAAAAtgAAAAAAAAAJAAAAQ29uZmlnUGFytgAAAAAAAADFAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC/AAAAAAAAAL8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 30, + 1, + "insert", + { + "characters": ".config" + }, + "BwAAAKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmAAAAAAAAAKYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 35, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAAAYCgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuY29uZi5jb25maWcgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCiMgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCmZyb20gc3RhY29zeXMuY29yZS5yc3MgaW1wb3J0IFJzcwpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIHJlcXVlc3QubWV0aG9kID09ICJQT1NUIjoKICAgICAgICB1c2VybmFtZSA9IHJlcXVlc3QuZm9ybS5nZXQoInVzZXJuYW1lIikKICAgICAgICBwYXNzd29yZCA9IHJlcXVlc3QuZm9ybS5nZXQoInBhc3N3b3JkIikKICAgICAgICBpZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgICAgICAgICBzZXNzaW9uWyJ1c2VyIl0gPSB1c2VybmFtZQogICAgICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiSWRlbnRpZmlhbnQgb3UgbW90IGRlIHBhc3NlIGluY29ycmVjdCIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKICAgICMgR0VUCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKCJsb2dpbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiKQoKCkBhcHAucm91dGUoIi93ZWIvbG9nb3V0IiwgbWV0aG9kcz1bIkdFVCJdKQpkZWYgbG9nb3V0KCk6CiAgICBzZXNzaW9uLnBvcCgidXNlciIpCiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBhZG1pbl9ob21lcGFnZSgpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIG5vdCAoCiAgICAgICAgInVzZXIiIGluIHNlc3Npb24KICAgICAgICBhbmQgc2Vzc2lvblsidXNlciJdID09IGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkKICAgICk6CiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJWb3VzIGF2ZXogw6l0w6kgZMOpY29ubmVjdMOpLiIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKCiAgICBjb21tZW50cyA9IGRhby5maW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKQogICAgcmV0dXJuIHJlbmRlcl90ZW1wbGF0ZSgKICAgICAgICAiYWRtaW5fIiArIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkxBTkcpICsgIi5odG1sIiwKICAgICAgICBjb21tZW50cz1jb21tZW50cywKICAgICAgICBiYXNldXJsPWNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfVVJMKSwKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2FkbWluIiwgbWV0aG9kcz1bIlBPU1QiXSkKZGVmIGFkbWluX2FjdGlvbigpOgogICAgcnNzID0gUnNzKCkKICAgIGNvbW1lbnQgPSBkYW8uZmluZF9jb21tZW50X2J5X2lkKHJlcXVlc3QuZm9ybS5nZXQoImNvbW1lbnQiKSkKICAgIGlmIGNvbW1lbnQgaXMgTm9uZToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIkNvbW1lbnRhaXJlIGludHJvdXZhYmxlIikKICAgIGVsaWYgcmVxdWVzdC5mb3JtLmdldCgiYWN0aW9uIikgPT0gIkFQUFJPVkUiOgogICAgICAgIGRhby5wdWJsaXNoX2NvbW1lbnQoY29tbWVudCkKICAgICAgICByc3MuZ2VuZXJhdGUoKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgcHVibGnDqSIpCiAgICBlbHNlOgogICAgICAgIGRhby5kZWxldGVfY29tbWVudChjb21tZW50KQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgc3VwcHJpbcOpIikKICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCgAAAAAAAAAAEwoAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACnAAAAAAAAAK0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 39, + 1, + "left_delete", + null, + "AQAAALUAAAAAAAAAtQAAAAAAAAAIAAAAQ29uZmlnLCA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC1AAAAAAAAAL0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 41, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BQAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACVAAAAAAAAAAAAAACVAAAAAAAAAJYAAAAAAAAAAAAAAJYAAAAAAAAAlwAAAAAAAAAAAAAAlwAAAAAAAACYAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAACQckA" + ], + [ + 42, + 1, + "insert", + { + "characters": " stacosys" + }, + "CQAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAACaAAAAAAAAAJsAAAAAAAAAAAAAAJsAAAAAAAAAnAAAAAAAAAAAAAAAnAAAAAAAAACdAAAAAAAAAAAAAACdAAAAAAAAAJ4AAAAAAAAAAAAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAAnwAAAAAAAACgAAAAAAAAAAAAAACgAAAAAAAAAKEAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACYAAAAAAAAAJgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 43, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAChAAAAAAAAAKEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 44, + 1, + "insert", + { + "characters": " core" + }, + "BQAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAKsAAAAAAAAArAAAAAAAAAAAAAAArAAAAAAAAACtAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 49, + 1, + "insert", + { + "characters": "re" + }, + "AwAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAIAAABuZr8AAAAAAAAAwAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC+AAAAAAAAAMAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 56, + 1, + "right_delete", + null, + "AQAAAFMCAAAAAAAAUwIAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAgAAAAAAAFMCAAAAAAAAAAAAAAAAQkA" + ], + [ + 58, + 1, + "left_delete", + null, + "AQAAAE4CAAAAAAAATgIAAAAAAAABAAAACg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABPAgAAAAAAAE8CAAAAAAAAAAAAAAAAAAA" + ], + [ + 61, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAKsCAAAAAAAArAIAAAAAAAAAAAAArAIAAAAAAACtAgAAAAAAAAAAAACtAgAAAAAAAK4CAAAAAAAAAAAAAK4CAAAAAAAArwIAAAAAAAAAAAAArwIAAAAAAACwAgAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAgAAAAAAAKsCAAAAAAAAAAAAAAAAUkA" + ], + [ + 64, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAPECAAAAAAAA8gIAAAAAAAAAAAAA8gIAAAAAAADzAgAAAAAAAAAAAADzAgAAAAAAAPQCAAAAAAAAAAAAAPQCAAAAAAAA9QIAAAAAAAAAAAAA9QIAAAAAAAD2AgAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADxAgAAAAAAAPECAAAAAAAAAAAAAAAA8L8" + ], + [ + 68, + 1, + "right_delete", + null, + "AQAAAHQDAAAAAAAAdAMAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAwAAAAAAAHQDAAAAAAAAAAAAAAAAQkA" + ], + [ + 70, + 1, + "left_delete", + null, + "AQAAAG8DAAAAAAAAbwMAAAAAAAABAAAACg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwAwAAAAAAAHADAAAAAAAAAAAAAAAAAAA" + ], + [ + 74, + 1, + "insert", + { + "characters": "o" + }, + "AQAAABsFAAAAAAAAHAUAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAbBQAAAAAAABsFAAAAAAAAAAAAAAAA8L8" + ], + [ + 75, + 1, + "left_delete", + null, + "AQAAABsFAAAAAAAAGwUAAAAAAAABAAAAbw", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAcBQAAAAAAABwFAAAAAAAAAAAAAAAA8L8" + ], + [ + 76, + 1, + "insert", + { + "characters": "core." + }, + "BQAAABsFAAAAAAAAHAUAAAAAAAAAAAAAHAUAAAAAAAAdBQAAAAAAAAAAAAAdBQAAAAAAAB4FAAAAAAAAAAAAAB4FAAAAAAAAHwUAAAAAAAAAAAAAHwUAAAAAAAAgBQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAbBQAAAAAAABsFAAAAAAAAAAAAAAAA8L8" + ], + [ + 82, + 1, + "right_delete", + null, + "AQAAAAcGAAAAAAAABwYAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAYBgAAAAAAAAcGAAAAAAAAAAAAAAAAQkA" + ], + [ + 84, + 1, + "left_delete", + null, + "AQAAAAIGAAAAAAAAAgYAAAAAAAABAAAACg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAADBgAAAAAAAAMGAAAAAAAAAAAAAAAAAAA" + ], + [ + 89, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAE0GAAAAAAAATgYAAAAAAAAAAAAATgYAAAAAAABPBgAAAAAAAAAAAABPBgAAAAAAAFAGAAAAAAAAAAAAAFAGAAAAAAAAUQYAAAAAAAAAAAAAUQYAAAAAAABSBgAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABNBgAAAAAAAE0GAAAAAAAAAAAAAAAA8L8" + ], + [ + 94, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAFAHAAAAAAAAUQcAAAAAAAAAAAAAUQcAAAAAAABSBwAAAAAAAAAAAABSBwAAAAAAAFMHAAAAAAAAAAAAAFMHAAAAAAAAVAcAAAAAAAAAAAAAVAcAAAAAAABVBwAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABQBwAAAAAAAFAHAAAAAAAAAAAAAAAA8L8" + ], + [ + 98, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAKwHAAAAAAAArQcAAAAAAAAAAAAArQcAAAAAAACuBwAAAAAAAAAAAACuBwAAAAAAAK8HAAAAAAAAAAAAAK8HAAAAAAAAsAcAAAAAAAAAAAAAsAcAAAAAAACxBwAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsBwAAAAAAAKwHAAAAAAAAAAAAAAAA8L8" + ], + [ + 109, + 1, + "right_delete", + null, + "AQAAAG8DAAAAAAAAbwMAAAAAAAAEAAAAICAgIA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAwAAAAAAAHMDAAAAAAAA////////738" + ], + [ + 113, + 1, + "left_delete", + null, + "AQAAAG4DAAAAAAAAbgMAAAAAAAABAAAAOg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAwAAAAAAAG8DAAAAAAAAAAAAAAAAW0A" + ], + [ + 114, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAG4DAAAAAAAAbwMAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAwAAAAAAAG4DAAAAAAAAAAAAAAAA8L8" + ], + [ + 124, + 1, + "paste", + null, + "AgAAAK4AAAAAAAAA5QAAAAAAAAAAAAAA5QAAAAAAAADlAAAAAAAAADAAAABmcm9tIHN0YWNvc3lzLmNvcmUuY29uZmlnIGltcG9ydCBDb25maWdQYXJhbWV0ZXI", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADeAAAAAAAAAK4AAAAAAAAAAAAAAAAAAAA" + ], + [ + 5, + 1, + "insert", + { + "characters": ".core" + }, + "BQAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAoQAAAAAAAAChAAAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACvAAAAAAAAAAQAAABjb3JlrwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAAsgAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" + ], + [ + 22, + 1, + "right_delete", + null, + "AQAAALkCAAAAAAAAuQIAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAuQIAAAAAAAC+AgAAAAAAAAAAAAAAAPC/" + ], + [ + 28, + 1, + "right_delete", + null, + "AQAAAPoCAAAAAAAA+gIAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA+gIAAAAAAAD/AgAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "right_delete", + null, + "AQAAABsFAAAAAAAAGwUAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAGwUAAAAAAAAgBQAAAAAAAAAAAAAAAPC/" + ], + [ + 40, + 1, + "right_delete", + null, + "AQAAAEgGAAAAAAAASAYAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATQYAAAAAAABIBgAAAAAAAAAAAAAAAPC/" + ], + [ + 44, + 1, + "right_delete", + null, + "AQAAAEYHAAAAAAAARgcAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwcAAAAAAABGBwAAAAAAAAAAAAAAAPC/" + ], + [ + 48, + 1, + "right_delete", + null, + "AQAAAJ0HAAAAAAAAnQcAAAAAAAAFAAAAY29yZS4", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAogcAAAAAAACdBwAAAAAAAAAAAAAAAPC/" + ], + [ + 55, + 2, + "right_delete", + null, + "AgAAAO4AAAAAAAAA7gAAAAAAAAAgAAAAIyBpbXBvcnQgQ29uZmlnLCBDb25maWdQYXJhbWV0ZXLuAAAAAAAAAO4AAAAAAAAAAQAAAAo", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADgEAAAAAAADuAAAAAAAAAAAAAAAAAAAA" + ], + [ + 5, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACjAAAAAAAAAAQAAABjb3JlowAAAAAAAACkAAAAAAAAAAAAAACkAAAAAAAAAKUAAAAAAAAAAAAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAKgAAAAAAAAAqQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKIAAAAAAAAApgAAAAAAAAAAAAAAAADwvw" + ], + [ + 9, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAMYAAAAAAAAAxwAAAAAAAAAAAAAAxwAAAAAAAADHAAAAAAAAAAQAAABjb3JlxwAAAAAAAADIAAAAAAAAAAAAAADIAAAAAAAAAMkAAAAAAAAAAAAAAMkAAAAAAAAAygAAAAAAAAAAAAAAygAAAAAAAADLAAAAAAAAAAAAAADLAAAAAAAAAMwAAAAAAAAAAAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMoAAAAAAAAAxgAAAAAAAAAAAAAAAADwvw" + ], + [ + 12, + 1, + "insert", + { + "characters": "," + }, + "AQAAALcAAAAAAAAAuAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtwAAAAAAAAAAAAAAAADwvw" + ], + [ + 13, + 1, + "insert", + { + "characters": " rss" + }, + "BAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAADwvw" + ], + [ + 16, + 1, + "right_delete", + null, + "AQAAAPkAAAAAAAAA+QAAAAAAAAAhAAAAZnJvbSBzdGFjb3N5cy5jb3JlLnJzcyBpbXBvcnQgUnNz", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABoBAAAAAAAA+QAAAAAAAAAAAAAAAAAAAA" + ], + [ + 18, + 2, + "right_delete", + null, + "AgAAAPgAAAAAAAAA+AAAAAAAAAABAAAACvgAAAAAAAAA+AAAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAA" + ], + [ + 23, + 1, + "right_delete", + null, + "AQAAANUHAAAAAAAA1QcAAAAAAAALAAAAcnNzID0gUnNzKCk", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOAHAAAAAAAA1QcAAAAAAAAAAAAAAABCQA" + ], + [ + 25, + 1, + "left_delete", + null, + "AQAAANAHAAAAAAAA0AcAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANEHAAAAAAAA0QcAAAAAAAAAAAAAAAAAAA" + ], + [ + 28, + 1, + "revert", + null, + "BAAAAAAAAAAAAAAAAAAAAAAAAAC6CQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuc2VydmljZSBpbXBvcnQgY29uZmlnLCByc3MKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOiAgICAKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgaWYgcmVxdWVzdC5tZXRob2QgPT0gIlBPU1QiOgogICAgICAgIHVzZXJuYW1lID0gcmVxdWVzdC5mb3JtLmdldCgidXNlcm5hbWUiKQogICAgICAgIHBhc3N3b3JkID0gcmVxdWVzdC5mb3JtLmdldCgicGFzc3dvcmQiKQogICAgICAgIGlmIGlzX2xvZ2luX29rKHVzZXJuYW1lLCBwYXNzd29yZCk6CiAgICAgICAgICAgIHNlc3Npb25bInVzZXIiXSA9IHVzZXJuYW1lCiAgICAgICAgICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJJZGVudGlmaWFudCBvdSBtb3QgZGUgcGFzc2UgaW5jb3JyZWN0IikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQogICAgIyBHRVQKICAgIHJldHVybiByZW5kZXJfdGVtcGxhdGUoImxvZ2luXyIgKyBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5MQU5HKSArICIuaHRtbCIpCgoKQGFwcC5yb3V0ZSgiL3dlYi9sb2dvdXQiLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBsb2dvdXQoKToKICAgIHNlc3Npb24ucG9wKCJ1c2VyIikKICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCgoKQGFwcC5yb3V0ZSgiL3dlYi9hZG1pbiIsIG1ldGhvZHM9WyJHRVQiXSkKZGVmIGFkbWluX2hvbWVwYWdlKCk6ICAgIAogICAgaWYgbm90ICgKICAgICAgICAidXNlciIgaW4gc2Vzc2lvbgogICAgICAgIGFuZCBzZXNzaW9uWyJ1c2VyIl0gPT0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1VTRVJOQU1FKQogICAgKToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIlZvdXMgYXZleiDDqXTDqSBkw6ljb25uZWN0w6kuIikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQoKICAgIGNvbW1lbnRzID0gZGFvLmZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKAogICAgICAgICJhZG1pbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiLAogICAgICAgIGNvbW1lbnRzPWNvbW1lbnRzLAogICAgICAgIGJhc2V1cmw9Y29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgYWRtaW5fYWN0aW9uKCk6ICAgIAogICAgY29tbWVudCA9IGRhby5maW5kX2NvbW1lbnRfYnlfaWQocmVxdWVzdC5mb3JtLmdldCgiY29tbWVudCIpKQogICAgaWYgY29tbWVudCBpcyBOb25lOgogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgaW50cm91dmFibGUiKQogICAgZWxpZiByZXF1ZXN0LmZvcm0uZ2V0KCJhY3Rpb24iKSA9PSAiQVBQUk9WRSI6CiAgICAgICAgZGFvLnB1Ymxpc2hfY29tbWVudChjb21tZW50KQogICAgICAgIHJzcy5nZW5lcmF0ZSgpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBwdWJsacOpIikKICAgIGVsc2U6CiAgICAgICAgZGFvLmRlbGV0ZV9jb21tZW50KGNvbW1lbnQpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBzdXBwcmltw6kiKQogICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2FkbWluIikKAAAAAAAAAACoCQAAAAAAAAAAAAAAAAAAAAAAAKgJAAAAAAAAAAAAAAAAAAAAAAAAqAkAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANAHAAAAAAAA0AcAAAAAAAAAAAAAAADwvw" ] ] }, { - "file": "stacosys/model/comment.py", + "file": "tests/test_config.py", "settings": { - "buffer_size": 471, + "buffer_size": 1382, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAEoAAAAAAAAASwAAAAAAAAAAAAAASwAAAAAAAABLAAAAAAAAAAQAAABjb25mSwAAAAAAAABMAAAAAAAAAAAAAABMAAAAAAAAAE0AAAAAAAAAAAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAASgAAAAAAAAAAAAAAAADwvw" + ], + [ + 11, + 1, + "insert", + { + "characters": "uration" + }, + "BwAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAABdAAAAAAAAAF4AAAAAAAAAAAAAAF4AAAAAAAAAXwAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" + ], + [ + 15, + 2, + "right_delete", + null, + "AgAAAGcAAAAAAAAAZwAAAAAAAAAHAAAAQ29uZmlnLGcAAAAAAAAAZwAAAAAAAAABAAAAIA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGcAAAAAAAAAbgAAAAAAAAAAAAAAAADwvw" + ], + [ + 17, + 1, + "insert", + { + "characters": "\nimport" + }, + "BwAAADsAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD4AAAAAAAAAAAAAAD4AAAAAAAAAPwAAAAAAAAAAAAAAPwAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEEAAAAAAAAAAAAAAEEAAAAAAAAAQgAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADsAAAAAAAAAOwAAAAAAAAAAAAAAADB4QA" + ], + [ + 18, + 1, + "insert", + { + "characters": " st" + }, + "AwAAAEIAAAAAAAAAQwAAAAAAAAAAAAAAQwAAAAAAAABEAAAAAAAAAAAAAABEAAAAAAAAAEUAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEIAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" + ], + [ + 19, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"stacosys\",\"kind\":9,\"sortText\":\"02.9999.stacosys\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "stacosys" + }, + "AgAAAEMAAAAAAAAAQwAAAAAAAAACAAAAc3RDAAAAAAAAAEsAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEUAAAAAAAAARQAAAAAAAAAAAAAAAADwvw" + ], + [ + 20, + 1, + "insert", + { + "characters": ".core" + }, + "BQAAAEsAAAAAAAAATAAAAAAAAAAAAAAATAAAAAAAAABNAAAAAAAAAAAAAABNAAAAAAAAAE4AAAAAAAAAAAAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEsAAAAAAAAASwAAAAAAAAAAAAAAAADwvw" + ], + [ + 21, + 4, + "left_delete", + null, + "BAAAAE8AAAAAAAAATwAAAAAAAAABAAAAZU4AAAAAAAAATgAAAAAAAAABAAAAck0AAAAAAAAATQAAAAAAAAABAAAAb0wAAAAAAAAATAAAAAAAAAABAAAAYw", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFAAAAAAAAAAUAAAAAAAAAAAAAAAAADwvw" + ], + [ + 22, + 1, + "insert", + { + "characters": "ser" + }, + "AwAAAEwAAAAAAAAATQAAAAAAAAAAAAAATQAAAAAAAABOAAAAAAAAAAAAAABOAAAAAAAAAE8AAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAADwvw" + ], + [ + 23, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"service\",\"kind\":9,\"sortText\":\"02.9999.service\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "service" + }, + "AgAAAEwAAAAAAAAATAAAAAAAAAADAAAAc2VyTAAAAAAAAABTAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" + ], + [ + 24, + 1, + "insert", + { + "characters": "." + }, + "AQAAAFMAAAAAAAAAVAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFMAAAAAAAAAUwAAAAAAAAAAAAAAAADwvw" + ], + [ + 27, + 1, + "insert", + { + "characters": "from" + }, + "BQAAADwAAAAAAAAAPQAAAAAAAAAAAAAAPQAAAAAAAAA9AAAAAAAAAAYAAABpbXBvcnQ9AAAAAAAAAD4AAAAAAAAAAAAAAD4AAAAAAAAAPwAAAAAAAAAAAAAAPwAAAAAAAABAAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADwAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" + ], + [ + 28, + 1, + "insert", + { + "characters": " " + }, + "AQAAAEAAAAAAAAAAQQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAADwvw" + ], + [ + 31, + 1, + "left_delete", + null, + "AQAAAEEAAAAAAAAAQQAAAAAAAAABAAAAIA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEIAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" + ], + [ + 33, + 1, + "left_delete", + null, + "AQAAAFEAAAAAAAAAUQAAAAAAAAABAAAALg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" + ], + [ + 34, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABWAAAAAAAAAAAAAABWAAAAAAAAAFcAAAAAAAAAAAAAAFcAAAAAAAAAWAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFEAAAAAAAAAUQAAAAAAAAAAAAAAAADwvw" + ], + [ + 35, + 1, + "insert", + { + "characters": " " + }, + "AQAAAFgAAAAAAAAAWQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" + ], + [ + 39, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"config\",\"data\":{\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"config\",\"position\":{\"character\":29,\"line\":5},\"filePath\":\"/home/yannic/work/stacosys/tests/test_config.py\"},\"kind\":6,\"sortText\":\"09.9999.config\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "config" + }, + "AQAAAFkAAAAAAAAAXwAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" + ], + [ + 58, + 1, + "right_delete", + null, + "AQAAADkBAAAAAAAAOQEAAAAAAAAUAAAAc2VsZi5jb25mID0gQ29uZmlnKCk", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0BAAAAAAAAOQEAAAAAAAAAAAAAAABSQA" + ], + [ + 60, + 1, + "left_delete", + null, + "AQAAADABAAAAAAAAMAEAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADEBAAAAAAAAMQEAAAAAAAAAAAAAAAAAAA" + ], + [ + 64, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAEEBAAAAAAAAQgEAAAAAAAAAAAAAQgEAAAAAAABCAQAAAAAAAAkAAABzZWxmLmNvbmZCAQAAAAAAAEMBAAAAAAAAAAAAAEMBAAAAAAAARAEAAAAAAAAAAAAARAEAAAAAAABFAQAAAAAAAAAAAABFAQAAAAAAAEYBAAAAAAAAAAAAAEYBAAAAAAAARwEAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoBAAAAAAAAQQEAAAAAAAAAAAAAAADwvw" + ], + [ + 68, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAI0BAAAAAAAAjgEAAAAAAAAAAAAAjgEAAAAAAACOAQAAAAAAAAkAAABzZWxmLmNvbmaOAQAAAAAAAI8BAAAAAAAAAAAAAI8BAAAAAAAAkAEAAAAAAAAAAAAAkAEAAAAAAACRAQAAAAAAAAAAAACRAQAAAAAAAJIBAAAAAAAAAAAAAJIBAAAAAAAAkwEAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJYBAAAAAAAAjQEAAAAAAAAAAAAAAABSQA" + ], + [ + 79, + 1, + "paste", + null, + "AgAAAPsBAAAAAAAAAQIAAAAAAAAAAAAAAQIAAAAAAAABAgAAAAAAAAkAAABzZWxmLmNvbmY", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPsBAAAAAAAABAIAAAAAAAAAAAAAAADwvw" + ], + [ + 84, + 1, + "paste", + null, + "AgAAAGkCAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABvAgAAAAAAAAkAAABzZWxmLmNvbmY", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHICAAAAAAAAaQIAAAAAAAAAAAAAAADwvw" + ], + [ + 89, + 1, + "paste", + null, + "AgAAANACAAAAAAAA1gIAAAAAAAAAAAAA1gIAAAAAAADWAgAAAAAAAAkAAABzZWxmLmNvbmY", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANACAAAAAAAA2QIAAAAAAAAAAAAAAADwvw" + ], + [ + 94, + 1, + "paste", + null, + "AgAAACEDAAAAAAAAJwMAAAAAAAAAAAAAJwMAAAAAAAAnAwAAAAAAAAkAAABzZWxmLmNvbmY", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAACoDAAAAAAAAIQMAAAAAAAAAAAAAAADwvw" + ], + [ + 99, + 1, + "paste", + null, + "AgAAAIMDAAAAAAAAiQMAAAAAAAAAAAAAiQMAAAAAAACJAwAAAAAAAAkAAABzZWxmLmNvbmY", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMDAAAAAAAAjAMAAAAAAAAAAAAAAADwvw" + ], + [ + 145, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAM0DAAAAAAAAzgMAAAAAAAAAAAAAzgMAAAAAAADOAwAAAAAAAAkAAABzZWxmLmNvbmbOAwAAAAAAAM8DAAAAAAAAAAAAAM8DAAAAAAAA0AMAAAAAAAAAAAAA0AMAAAAAAADRAwAAAAAAAAAAAADRAwAAAAAAANIDAAAAAAAAAAAAANIDAAAAAAAA0wMAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANYDAAAAAAAAzQMAAAAAAAAAAAAAAADwvw" + ], + [ + 150, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAIIEAAAAAAAAgwQAAAAAAAAAAAAAgwQAAAAAAACDBAAAAAAAAAkAAABzZWxmLmNvbmaDBAAAAAAAAIQEAAAAAAAAAAAAAIQEAAAAAAAAhQQAAAAAAAAAAAAAhQQAAAAAAACGBAAAAAAAAAAAAACGBAAAAAAAAIcEAAAAAAAAAAAAAIcEAAAAAAAAiAQAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIEAAAAAAAAiwQAAAAAAAAAAAAAAADwvw" + ], + [ + 155, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAK8EAAAAAAAAsAQAAAAAAAAAAAAAsAQAAAAAAACwBAAAAAAAAAkAAABzZWxmLmNvbmawBAAAAAAAALEEAAAAAAAAAAAAALEEAAAAAAAAsgQAAAAAAAAAAAAAsgQAAAAAAACzBAAAAAAAAAAAAACzBAAAAAAAALQEAAAAAAAAAAAAALQEAAAAAAAAtQQAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgEAAAAAAAArwQAAAAAAAAAAAAAAABSQA" + ], + [ + 160, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAPcEAAAAAAAA+AQAAAAAAAAAAAAA+AQAAAAAAAD4BAAAAAAAAAkAAABzZWxmLmNvbmb4BAAAAAAAAPkEAAAAAAAAAAAAAPkEAAAAAAAA+gQAAAAAAAAAAAAA+gQAAAAAAAD7BAAAAAAAAAAAAAD7BAAAAAAAAPwEAAAAAAAAAAAAAPwEAAAAAAAA/QQAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPcEAAAAAAAAAAUAAAAAAAAAAAAAAADwvw" + ], + [ + 164, + 1, + "insert", + { + "characters": "config" + }, + "BwAAADUFAAAAAAAANgUAAAAAAAAAAAAANgUAAAAAAAA2BQAAAAAAAAkAAABzZWxmLmNvbmY2BQAAAAAAADcFAAAAAAAAAAAAADcFAAAAAAAAOAUAAAAAAAAAAAAAOAUAAAAAAAA5BQAAAAAAAAAAAAA5BQAAAAAAADoFAAAAAAAAAAAAADoFAAAAAAAAOwUAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUFAAAAAAAAPgUAAAAAAAAAAAAAAADwvw" + ] + ] + }, + { + "file": "Dockerfile", + "settings": + { + "buffer_size": 716, "line_ending": "Unix" } }, { - "file": "stacosys/conf/config.py", + "file": "tests/test_api.py", "settings": { - "buffer_size": 2481, + "buffer_size": 1634, "encoding": "UTF-8", "line_ending": "Unix" } + }, + { + "file": "stacosys/service/__init__.py", + "settings": + { + "buffer_size": 180, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 4, + 1, + "insert", + { + "characters": "from" + }, + "BAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 5, + 1, + "insert", + { + "characters": " .maile" + }, + "BwAAAFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAFgAAAAAAAAAAAAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 6, + 1, + "insert", + { + "characters": "r" + }, + "AQAAAFwAAAAAAAAAXQAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" + ], + [ + 7, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "insert", + { + "characters": " Mailer" + }, + "BwAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABpAAAAAAAAAAAAAABpAAAAAAAAAGoAAAAAAAAAAAAAAGoAAAAAAAAAawAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert", + { + "characters": "mailer" + }, + "BgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAAAA" + ], + [ + 12, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 1, + "insert", + { + "characters": " " + }, + "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 17, + 2, + "left_delete", + null, + "AgAAAFwAAAAAAAAAXAAAAAAAAAABAAAAclsAAAAAAAAAWwAAAAAAAAABAAAAZQ", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" + ], + [ + 22, + 1, + "insert", + { + "characters": "Mailer" + }, + "BgAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAIwAAAAAAAAAjgAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjAAAAAAAAACMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 33, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BQAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABuAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" + ], + [ + 34, + 1, + "insert", + { + "characters": " .rss" + }, + "BQAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbgAAAAAAAABuAAAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "insert", + { + "characters": "feed" + }, + "BAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB1AAAAAAAAAAAAAAB1AAAAAAAAAHYAAAAAAAAAAAAAAHYAAAAAAAAAdwAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 36, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAHcAAAAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAB5AAAAAAAAAAAAAAB5AAAAAAAAAHoAAAAAAAAAAAAAAHoAAAAAAAAAewAAAAAAAAAAAAAAewAAAAAAAAB8AAAAAAAAAAAAAAB8AAAAAAAAAH0AAAAAAAAAAAAAAH0AAAAAAAAAfgAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwAAAAAAAAB3AAAAAAAAAAAAAAAAAPC/" + ], + [ + 37, + 1, + "insert", + { + "characters": " " + }, + "AQAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfgAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" + ], + [ + 38, + 1, + "insert", + { + "characters": "Rss" + }, + "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 41, + 1, + "insert", + { + "characters": "\nrss" + }, + "BAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACpAAAAAAAAAAAAAACpAAAAAAAAAKoAAAAAAAAAAAAAAKoAAAAAAAAAqwAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAGtA" + ], + [ + 42, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAKsAAAAAAAAArAAAAAAAAAAAAAAArAAAAAAAAACtAAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqwAAAAAAAACrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 43, + 1, + "insert", + { + "characters": " Rss" + }, + "BAAAAK0AAAAAAAAArgAAAAAAAAAAAAAArgAAAAAAAACvAAAAAAAAAAAAAACvAAAAAAAAALAAAAAAAAAAAAAAALAAAAAAAAAAsQAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArQAAAAAAAACtAAAAAAAAAAAAAAAAAPC/" + ], + [ + 44, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAALEAAAAAAAAAswAAAAAAAAAAAAAA", + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsQAAAAAAAACxAAAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "revert", + null, + "BAAAAAAAAAAAAAAAAAAAAAAAAACzAAAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIC5jb25maWd1cmF0aW9uIGltcG9ydCBDb25maWcKZnJvbSAubWFpbCBpbXBvcnQgTWFpbGVyCmZyb20gLnJzc2ZlZWQgaW1wb3J0IFJzcwoKY29uZmlnID0gQ29uZmlnKCkKbWFpbGVyID0gTWFpbGVyKCkKcnNzID0gUnNzKCkAAAAAAAAAALQAAAAAAAAAAAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAAAAAAAAAAAAC0AAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAlQAAAAAAAAAAAAAAAADwvw" + ] + ] + }, + { + "file": "stacosys/interface/form.py", + "settings": + { + "buffer_size": 2659, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "right_delete", + null, + "AQAAAJ0AAAAAAAAAnQAAAAAAAAAHAAAAQ29uZmlnLA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAKQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 9, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BQAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7AAAAAAAAAHsAAAAAAAAAAAAAAACQckA" + ], + [ + 10, + 1, + "insert", + { + "characters": " stacosys" + }, + "CQAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 11, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACJAAAAAAAAAIkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 12, + 1, + "insert", + { + "characters": " core" + }, + "BQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACSAAAAAAAAAAAAAACSAAAAAAAAAJMAAAAAAAAAAAAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACVAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACQAAAAAAAAAJAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 19, + 1, + "insert", + { + "characters": "core" + }, + "BQAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAAClAAAAAAAAAAQAAABjb25mpQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 24, + 1, + "right_delete", + null, + "AQAAAJ8BAAAAAAAAnwEAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACwAQAAAAAAAJ8BAAAAAAAAAAAAAAAAQkA" + ], + [ + 26, + 1, + "left_delete", + null, + "AQAAAJoBAAAAAAAAmgEAAAAAAAABAAAACg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAQAAAAAAAJsBAAAAAAAAAAAAAAAAAAA" + ], + [ + 31, + 1, + "insert", + { + "characters": "core." + }, + "BQAAALgFAAAAAAAAuQUAAAAAAAAAAAAAuQUAAAAAAAC6BQAAAAAAAAAAAAC6BQAAAAAAALsFAAAAAAAAAAAAALsFAAAAAAAAvAUAAAAAAAAAAAAAvAUAAAAAAAC9BQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC4BQAAAAAAALgFAAAAAAAAAAAAAAAA8L8" + ], + [ + 36, + 1, + "right_delete", + null, + "AQAAAAsHAAAAAAAACwcAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAcBwAAAAAAAAsHAAAAAAAAAAAAAAAAQkA" + ], + [ + 38, + 1, + "left_delete", + null, + "AQAAAAYHAAAAAAAABgcAAAAAAAABAAAACg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAHBwAAAAAAAAcHAAAAAAAAAAAAAAAAAAA" + ], + [ + 43, + 1, + "insert", + { + "characters": "core." + }, + "BQAAANMIAAAAAAAA1AgAAAAAAAAAAAAA1AgAAAAAAADVCAAAAAAAAAAAAADVCAAAAAAAANYIAAAAAAAAAAAAANYIAAAAAAAA1wgAAAAAAAAAAAAA1wgAAAAAAADYCAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTCAAAAAAAANMIAAAAAAAAAAAAAAAA8L8" + ], + [ + 49, + 1, + "insert", + { + "characters": "coer." + }, + "BQAAABoHAAAAAAAAGwcAAAAAAAAAAAAAGwcAAAAAAAAcBwAAAAAAAAAAAAAcBwAAAAAAAB0HAAAAAAAAAAAAAB0HAAAAAAAAHgcAAAAAAAAAAAAAHgcAAAAAAAAfBwAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAaBwAAAAAAABoHAAAAAAAAAAAAAAAA8L8" + ], + [ + 52, + 1, + "insert", + { + "characters": "re" + }, + "AwAAABwHAAAAAAAAHQcAAAAAAAAAAAAAHQcAAAAAAAAdBwAAAAAAAAIAAABlch0HAAAAAAAAHgcAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAeBwAAAAAAABwHAAAAAAAAAAAAAAAA8L8" + ], + [ + 57, + 1, + "paste", + null, + "AgAAAJYAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADNAAAAAAAAADEAAABmcm9tIHN0YWNvc3lzLmNvcmUuY29uZmlnIGltcG9ydCAgQ29uZmlnUGFyYW1ldGVy", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADHAAAAAAAAAJYAAAAAAAAAAAAAAAAAAAA" + ], + [ + 5, + 1, + "insert", + { + "characters": ".ser" + }, + "BAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIkAAAAAAAAAiQAAAAAAAAAAAAAAAADwvw" + ], + [ + 6, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"service\",\"kind\":9,\"sortText\":\"02.9999.service\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "service" + }, + "AgAAAIoAAAAAAAAAigAAAAAAAAADAAAAc2VyigAAAAAAAACRAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAADwvw" + ], + [ + 10, + 1, + "insert", + { + "characters": "config" + }, + "BwAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACaAAAAAAAAAAQAAABjb3JlmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAJwAAAAAAAAAnQAAAAAAAAAAAAAAnQAAAAAAAACeAAAAAAAAAAAAAACeAAAAAAAAAJ8AAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJ0AAAAAAAAAmQAAAAAAAAAAAAAAAADwvw" + ], + [ + 11, + 1, + "insert", + { + "characters": "," + }, + "AQAAAJ8AAAAAAAAAoAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJ8AAAAAAAAAnwAAAAAAAAAAAAAAAADwvw" + ], + [ + 12, + 1, + "insert", + { + "characters": " mailer" + }, + "BwAAAKAAAAAAAAAAoQAAAAAAAAAAAAAAoQAAAAAAAACiAAAAAAAAAAAAAACiAAAAAAAAAKMAAAAAAAAAAAAAAKMAAAAAAAAApAAAAAAAAAAAAAAApAAAAAAAAAClAAAAAAAAAAAAAAClAAAAAAAAAKYAAAAAAAAAAAAAAKYAAAAAAAAApwAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKAAAAAAAAAAoAAAAAAAAAAAAAAAAADwvw" + ], + [ + 16, + 1, + "right_delete", + null, + "AQAAAOAAAAAAAAAA4AAAAAAAAAAnAAAAZnJvbSBzdGFjb3N5cy5jb3JlLm1haWxlciBpbXBvcnQgTWFpbGVy", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAcBAAAAAAAA4AAAAAAAAAAAAAAAAAAAAA" + ], + [ + 17, + 1, + "left_delete", + null, + "AQAAAN8AAAAAAAAA3wAAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOAAAAAAAAAA4AAAAAAAAAAAAAAAAADwvw" + ], + [ + 20, + 1, + "insert", + { + "characters": "service" + }, + "CAAAALYAAAAAAAAAtwAAAAAAAAAAAAAAtwAAAAAAAAC3AAAAAAAAAAQAAABjb3JltwAAAAAAAAC4AAAAAAAAAAAAAAC4AAAAAAAAALkAAAAAAAAAAAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAugAAAAAAAAAAAAAAAADwvw" + ], + [ + 26, + 1, + "right_delete", + null, + "AQAAAKsFAAAAAAAAqwUAAAAAAAAFAAAAY29yZS4", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAFAAAAAAAAqwUAAAAAAAAAAAAAAADwvw" + ], + [ + 33, + 1, + "right_delete", + null, + "AQAAAAgHAAAAAAAACAcAAAAAAAAFAAAAY29yZS4", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAA0HAAAAAAAACAcAAAAAAAAAAAAAAADwvw" + ], + [ + 39, + 1, + "right_delete", + null, + "AQAAAJ8IAAAAAAAAnwgAAAAAAAARAAAAbWFpbGVyID0gTWFpbGVyKCk", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAIAAAAAAAAnwgAAAAAAAAAAAAAAABCQA" + ], + [ + 41, + 1, + "left_delete", + null, + "AQAAAJoIAAAAAAAAmggAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJsIAAAAAAAAmwgAAAAAAAAAAAAAAAAAAA" + ], + [ + 45, + 1, + "right_delete", + null, + "AQAAAK8IAAAAAAAArwgAAAAAAAAFAAAAY29yZS4", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQIAAAAAAAArwgAAAAAAAAAAAAAAADwvw" + ], + [ + 48, + 1, + "revert", + null, + "BAAAAAAAAAAAAAAAAAAAAAAAAABvCgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCmltcG9ydCBsb2dnaW5nCgppbXBvcnQgYmFja2dyb3VuZApmcm9tIGZsYXNrIGltcG9ydCBhYm9ydCwgcmVkaXJlY3QsIHJlcXVlc3QKCmZyb20gc3RhY29zeXMuc2VydmljZSBpbXBvcnQgY29uZmlnLCBtYWlsZXIKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgoKQGFwcC5yb3V0ZSgiL25ld2NvbW1lbnQiLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgbmV3X2Zvcm1fY29tbWVudCgpOiAgICAKICAgIGRhdGEgPSByZXF1ZXN0LmZvcm0KICAgIGxvZ2dlci5pbmZvKCJmb3JtIGRhdGEgJXMiLCBzdHIoZGF0YSkpCgogICAgIyBob25leXBvdCBmb3Igc3BhbW1lcnMKICAgIGNhcHRjaGEgPSBkYXRhLmdldCgicmVtYXJxdWUiLCAiIikKICAgIGlmIGNhcHRjaGE6CiAgICAgICAgbG9nZ2VyLndhcm5pbmcoImRpc2NhcmQgc3BhbTogZGF0YSAlcyIsIGRhdGEpCiAgICAgICAgYWJvcnQoNDAwKQoKICAgIHVybCA9IGRhdGEuZ2V0KCJ1cmwiLCAiIikKICAgIGF1dGhvcl9uYW1lID0gZGF0YS5nZXQoImF1dGhvciIsICIiKS5zdHJpcCgpCiAgICBhdXRob3JfZ3JhdmF0YXIgPSBkYXRhLmdldCgiZW1haWwiLCAiIikuc3RyaXAoKQogICAgYXV0aG9yX3NpdGUgPSBkYXRhLmdldCgic2l0ZSIsICIiKS5sb3dlcigpLnN0cmlwKCkKICAgIGlmIGF1dGhvcl9zaXRlIGFuZCBhdXRob3Jfc2l0ZVs6NF0gIT0gImh0dHAiOgogICAgICAgIGF1dGhvcl9zaXRlID0gImh0dHA6Ly8iICsgYXV0aG9yX3NpdGUKICAgIG1lc3NhZ2UgPSBkYXRhLmdldCgibWVzc2FnZSIsICIiKQoKICAgICMgYW50aS1zcGFtIGFnYWluCiAgICBpZiBub3QgdXJsIG9yIG5vdCBhdXRob3JfbmFtZSBvciBub3QgbWVzc2FnZToKICAgICAgICBsb2dnZXIud2FybmluZygiZW1wdHkgZmllbGQ6IGRhdGEgJXMiLCBkYXRhKQogICAgICAgIGFib3J0KDQwMCkKICAgIGlmIG5vdCBjaGVja19mb3JtX2RhdGEoZGF0YS50b19kaWN0KCkpOgogICAgICAgIGxvZ2dlci53YXJuaW5nKCJhZGRpdGlvbmFsIGZpZWxkOiBkYXRhICVzIiwgZGF0YSkKICAgICAgICBhYm9ydCg0MDApCgogICAgIyBhZGQgYSByb3cgdG8gQ29tbWVudCB0YWJsZQogICAgY29tbWVudCA9IGRhby5jcmVhdGVfY29tbWVudCgKICAgICAgICB1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlCiAgICApCgogICAgIyBzZW5kIG5vdGlmaWNhdGlvbiBlLW1haWwgYXN5bmNocm9ub3VzbHkKICAgIHN1Ym1pdF9uZXdfY29tbWVudChjb21tZW50KQoKICAgIHJldHVybiByZWRpcmVjdChjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TSVRFX1JFRElSRUNUKSwgY29kZT0zMDIpCgoKZGVmIGNoZWNrX2Zvcm1fZGF0YShwb3N0ZWRfY29tbWVudCk6CiAgICBmaWVsZHMgPSBbInVybCIsICJtZXNzYWdlIiwgInNpdGUiLCAicmVtYXJxdWUiLCAiYXV0aG9yIiwgInRva2VuIiwgImVtYWlsIl0KICAgIGZpbHRlcmVkID0gZGljdChmaWx0ZXIobGFtYmRhIHg6IHhbMF0gbm90IGluIGZpZWxkcywgcG9zdGVkX2NvbW1lbnQuaXRlbXMoKSkpCiAgICByZXR1cm4gbm90IGZpbHRlcmVkCgoKQGJhY2tncm91bmQudGFzawpkZWYgc3VibWl0X25ld19jb21tZW50KGNvbW1lbnQpOiAgICAKICAgIHNpdGVfdXJsID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpCiAgICBjb21tZW50X2xpc3QgPSAoCiAgICAgICAgZiJXZWIgYWRtaW4gaW50ZXJmYWNlOiB7c2l0ZV91cmx9L3dlYi9hZG1pbiIsCiAgICAgICAgIiIsCiAgICAgICAgZiJhdXRob3I6IHtjb21tZW50LmF1dGhvcl9uYW1lfSIsCiAgICAgICAgZiJzaXRlOiB7Y29tbWVudC5hdXRob3Jfc2l0ZX0iLAogICAgICAgIGYiZGF0ZToge2NvbW1lbnQuY3JlYXRlZH0iLAogICAgICAgIGYidXJsOiB7Y29tbWVudC51cmx9IiwKICAgICAgICAiIiwKICAgICAgICBjb21tZW50LmNvbnRlbnQsCiAgICAgICAgIiIsCiAgICApCiAgICBlbWFpbF9ib2R5ID0gIlxuIi5qb2luKGNvbW1lbnRfbGlzdCkKCiAgICAjIHNlbmQgZW1haWwgdG8gbm90aWZ5IGFkbWluICAgIAogICAgc2l0ZV9uYW1lID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9OQU1FKQogICAgc3ViamVjdCA9IGYiU1RBQ09TWVMge3NpdGVfbmFtZX0iCiAgICBpZiBtYWlsZXIuc2VuZChzdWJqZWN0LCBlbWFpbF9ib2R5KToKICAgICAgICBsb2dnZXIuZGVidWcoIm5ldyBjb21tZW50IHByb2Nlc3NlZCIpCgogICAgICAgICMgc2F2ZSBub3RpZmljYXRpb24gZGF0ZXRpbWUKICAgICAgICBkYW8ubm90aWZ5X2NvbW1lbnQoY29tbWVudCkKICAgIGVsc2U6CiAgICAgICAgbG9nZ2VyLndhcm5pbmcoInJlc2NoZWR1bGVkLiBzZW5kIG1haWwgZmFpbHVyZSAlcyIsIHN1YmplY3QpCgoKQGJhY2tncm91bmQuY2FsbGJhY2sKZGVmIHN1Ym1pdF9uZXdfY29tbWVudF9jYWxsYmFjayhmdXR1cmUpOgogICAgIyBUT0RPIHVzZSBmdXR1cmUgdG8gbG9nIHN1Ym1pdCBzdGF0dXMKICAgIGxvZ2dlci5kZWJ1ZyhmdXR1cmUpCgAAAAAAAAAAYwoAAAAAAAAAAAAAAAAAAAAAAABjCgAAAAAAAAAAAAAAAAAAAAAAAGMKAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADQJAAAAAAAANAkAAAAAAAAAAAAAAABiQA" + ] + ] + }, + { + "file": "stacosys/run.py", + "settings": + { + "buffer_size": 2789, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "insert", + { + "characters": "core" + }, + "BQAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABvAAAAAAAAAAQAAABjb25mbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 11, + 1, + "cut", + null, + "AQAAAIEAAAAAAAAAgQAAAAAAAAAHAAAAQ29uZmlnLA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 12, + 1, + "right_delete", + null, + "AQAAAIEAAAAAAAAAgQAAAAAAAAABAAAAIA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 14, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABfAAAAAAAAAF8AAAAAAAAAAAAAAACQckA" + ], + [ + 19, + 1, + "paste", + null, + "AQAAAGAAAAAAAAAAcgAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 20, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 21, + 1, + "insert", + { + "characters": " config" + }, + "BwAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 36, + 1, + "insert", + { + "characters": "import" + }, + "BwAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAQAAABmcm9tYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 41, + 1, + "right_delete", + null, + "AQAAAHQAAAAAAAAAdAAAAAAAAAAOAAAAIGltcG9ydCBjb25maWc", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 53, + 1, + "right_delete", + null, + "AQAAAJ0EAAAAAAAAnQQAAAAAAAAHAAAAY29uZiA9IA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkBAAAAAAAAJ0EAAAAAAAAAAAAAAAAQkA" + ], + [ + 54, + 1, + "insert", + { + "characters": "core.c" + }, + "BgAAAJ0EAAAAAAAAngQAAAAAAAAAAAAAngQAAAAAAACfBAAAAAAAAAAAAACfBAAAAAAAAKAEAAAAAAAAAAAAAKAEAAAAAAAAoQQAAAAAAAAAAAAAoQQAAAAAAACiBAAAAAAAAAAAAACiBAAAAAAAAKMEAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdBAAAAAAAAJ0EAAAAAAAAAAAAAAAA8L8" + ], + [ + 55, + 1, + "right_delete", + null, + "AQAAAKMEAAAAAAAAowQAAAAAAAABAAAAQw", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACjBAAAAAAAAKMEAAAAAAAAAAAAAAAA8L8" + ], + [ + 79, + 1, + "insert", + { + "characters": "from" + }, + "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAA" + ], + [ + 80, + 1, + "insert", + { + "characters": " " + }, + "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 82, + 2, + "right_delete", + null, + "AgAAAGUAAAAAAAAAZQAAAAAAAAAGAAAAaW1wb3J0ZQAAAAAAAABlAAAAAAAAAAEAAAAg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABlAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 84, + 1, + "insert", + { + "characters": " imo" + }, + "BAAAAG0AAAAAAAAAbgAAAAAAAAAAAAAAbgAAAAAAAABvAAAAAAAAAAAAAABvAAAAAAAAAHAAAAAAAAAAAAAAAHAAAAAAAAAAcQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABtAAAAAAAAAG0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 85, + 1, + "left_delete", + null, + "AQAAAHAAAAAAAAAAcAAAAAAAAAABAAAAbw", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABxAAAAAAAAAHEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 86, + 1, + "insert", + { + "characters": "port" + }, + "BAAAAHAAAAAAAAAAcQAAAAAAAAAAAAAAcQAAAAAAAAByAAAAAAAAAAAAAAByAAAAAAAAAHMAAAAAAAAAAAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwAAAAAAAAAHAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 87, + 1, + "right_delete", + null, + "AQAAAHQAAAAAAAAAdAAAAAAAAAABAAAALg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 88, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHQAAAAAAAAAdQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 97, + 1, + "insert", + { + "characters": "core" + }, + "BAAAAOYEAAAAAAAA5wQAAAAAAAAAAAAA5wQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOkEAAAAAAAAAAAAAOkEAAAAAAAA6gQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADmBAAAAAAAAOYEAAAAAAAAAAAAAAAA8L8" + ], + [ + 100, + 1, + "insert", + { + "characters": "." + }, + "AQAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBAAAAAAAAOoEAAAAAAAAAAAAAAAA8L8" + ], + [ + 102, + 1, + "insert", + { + "characters": "ig" + }, + "AgAAAO8EAAAAAAAA8AQAAAAAAAAAAAAA8AQAAAAAAADxBAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADvBAAAAAAAAO8EAAAAAAAAAAAAAAAA8L8" + ], + [ + 107, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAHwFAAAAAAAAfQUAAAAAAAAAAAAAfQUAAAAAAAB+BQAAAAAAAAAAAAB+BQAAAAAAAH8FAAAAAAAAAAAAAH8FAAAAAAAAgAUAAAAAAAAAAAAAgAUAAAAAAACBBQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB8BQAAAAAAAHwFAAAAAAAAAAAAAAAA8L8" + ], + [ + 110, + 1, + "insert", + { + "characters": "ig" + }, + "AgAAAIUFAAAAAAAAhgUAAAAAAAAAAAAAhgUAAAAAAACHBQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFBQAAAAAAAIUFAAAAAAAAAAAAAAAA8L8" + ], + [ + 116, + 1, + "insert", + { + "characters": "coer." + }, + "BQAAAOEFAAAAAAAA4gUAAAAAAAAAAAAA4gUAAAAAAADjBQAAAAAAAAAAAADjBQAAAAAAAOQFAAAAAAAAAAAAAOQFAAAAAAAA5QUAAAAAAAAAAAAA5QUAAAAAAADmBQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADhBQAAAAAAAOEFAAAAAAAAAAAAAAAA8L8" + ], + [ + 117, + 3, + "left_delete", + null, + "AwAAAOUFAAAAAAAA5QUAAAAAAAABAAAALuQFAAAAAAAA5AUAAAAAAAABAAAAcuMFAAAAAAAA4wUAAAAAAAABAAAAZQ", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADmBQAAAAAAAOYFAAAAAAAAAAAAAAAA8L8" + ], + [ + 118, + 1, + "insert", + { + "characters": "re." + }, + "AwAAAOMFAAAAAAAA5AUAAAAAAAAAAAAA5AUAAAAAAADlBQAAAAAAAAAAAADlBQAAAAAAAOYFAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADjBQAAAAAAAOMFAAAAAAAAAAAAAAAA8L8" + ], + [ + 120, + 1, + "insert", + { + "characters": "ig" + }, + "AgAAAOoFAAAAAAAA6wUAAAAAAAAAAAAA6wUAAAAAAADsBQAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" + ], + [ + 126, + 1, + "insert", + { + "characters": "core." + }, + "BQAAAEwHAAAAAAAATQcAAAAAAAAAAAAATQcAAAAAAABOBwAAAAAAAAAAAABOBwAAAAAAAE8HAAAAAAAAAAAAAE8HAAAAAAAAUAcAAAAAAAAAAAAAUAcAAAAAAABRBwAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMBwAAAAAAAEwHAAAAAAAAAAAAAAAA8L8" + ], + [ + 127, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"symbolLabel\":\"Config\",\"workspacePath\":\"/home/yannic/work/stacosys\",\"position\":{\"character\":13,\"line\":62},\"filePath\":\"/home/yannic/work/stacosys/stacosys/run.py\"},\"label\":\"Config\",\"sortText\":\"05.0000.Config\",\"kind\":7},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "Config" + }, + "AQAAAFEHAAAAAAAAVwcAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABRBwAAAAAAAFEHAAAAAAAAAAAAAAAA8L8" + ], + [ + 129, + 1, + "right_delete", + null, + "AQAAAFcHAAAAAAAAVwcAAAAAAAAEAAAAY29uZg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABXBwAAAAAAAFsHAAAAAAAAAAAAAAAA8L8" + ], + [ + 134, + 1, + "insert", + { + "characters": "c" + }, + "AQAAAFEHAAAAAAAAUgcAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABRBwAAAAAAAFEHAAAAAAAAAAAAAAAA8L8" + ], + [ + 135, + 1, + "right_delete", + null, + "AQAAAFIHAAAAAAAAUgcAAAAAAAABAAAAQw", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABSBwAAAAAAAFIHAAAAAAAAAAAAAAAA8L8" + ], + [ + 146, + 1, + "paste", + null, + "AgAAAHsHAAAAAAAAhgcAAAAAAAAAAAAAhgcAAAAAAACGBwAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7BwAAAAAAAH8HAAAAAAAAAAAAAAAA8L8" + ], + [ + 150, + 1, + "paste", + null, + "AgAAAK4HAAAAAAAAuQcAAAAAAAAAAAAAuQcAAAAAAAC5BwAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuBwAAAAAAALIHAAAAAAAAAAAAAAAA8L8" + ], + [ + 154, + 1, + "paste", + null, + "AgAAAOIHAAAAAAAA7QcAAAAAAAAAAAAA7QcAAAAAAADtBwAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADiBwAAAAAAAOYHAAAAAAAAAAAAAAAA8L8" + ], + [ + 158, + 1, + "paste", + null, + "AgAAABYIAAAAAAAAIQgAAAAAAAAAAAAAIQgAAAAAAAAhCAAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAWCAAAAAAAABoIAAAAAAAAAAAAAAAA8L8" + ], + [ + 170, + 1, + "paste", + null, + "AgAAAKYIAAAAAAAAsQgAAAAAAAAAAAAAsQgAAAAAAACxCAAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmCAAAAAAAAKoIAAAAAAAAAAAAAAAA8L8" + ], + [ + 175, + 1, + "paste", + null, + "AgAAANoIAAAAAAAA5QgAAAAAAAAAAAAA5QgAAAAAAADlCAAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADaCAAAAAAAAN4IAAAAAAAAAAAAAAAA8L8" + ], + [ + 180, + 1, + "paste", + null, + "AgAAABIJAAAAAAAAHQkAAAAAAAAAAAAAHQkAAAAAAAAdCQAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASCQAAAAAAABYJAAAAAAAAAAAAAAAA8L8" + ], + [ + 185, + 1, + "paste", + null, + "AgAAAEcJAAAAAAAAUgkAAAAAAAAAAAAAUgkAAAAAAABSCQAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABHCQAAAAAAAEsJAAAAAAAAAAAAAAAA8L8" + ], + [ + 190, + 1, + "paste", + null, + "AgAAAH8JAAAAAAAAigkAAAAAAAAAAAAAigkAAAAAAACKCQAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/CQAAAAAAAIMJAAAAAAAAAAAAAAAA8L8" + ], + [ + 195, + 1, + "paste", + null, + "AgAAACUKAAAAAAAAMAoAAAAAAAAAAAAAMAoAAAAAAAAwCgAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAlCgAAAAAAACkKAAAAAAAAAAAAAAAA8L8" + ], + [ + 200, + 1, + "paste", + null, + "AgAAAF4KAAAAAAAAaQoAAAAAAAAAAAAAaQoAAAAAAABpCgAAAAAAAAQAAABjb25m", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABeCgAAAAAAAGIKAAAAAAAAAAAAAAAA8L8" + ], + [ + 216, + 1, + "insert", + { + "characters": "." + }, + "AQAAAG0AAAAAAAAAbgAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABtAAAAAAAAAG0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 219, + 1, + "right_delete", + null, + "AQAAAG4AAAAAAAAAbgAAAAAAAAAIAAAAIGltcG9ydCA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAAAAAAAAAHYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 226, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 227, + 1, + "insert", + { + "characters": " confi" + }, + "BgAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 228, + 1, + "insert", + { + "characters": "g" + }, + "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 241, + 1, + "insert", + { + "characters": "import" + }, + "BwAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAQAAABmcm9tYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 251, + 1, + "insert", + { + "characters": "." + }, + "AgAAAHQAAAAAAAAAdQAAAAAAAAAAAAAAdQAAAAAAAAB1AAAAAAAAAAgAAAAgaW1wb3J0IA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 258, + 1, + "right_delete", + null, + "AQAAAKQEAAAAAAAApAQAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACpBAAAAAAAAKQEAAAAAAAAAAAAAAAA8L8" + ], + [ + 265, + 1, + "right_delete", + null, + "AQAAAOMEAAAAAAAA4wQAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADjBAAAAAAAAOgEAAAAAAAAAAAAAAAA8L8" + ], + [ + 270, + 1, + "right_delete", + null, + "AQAAAHQFAAAAAAAAdAUAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5BQAAAAAAAHQFAAAAAAAAAAAAAAAA8L8" + ], + [ + 297, + 1, + "insert", + { + "characters": "from" + }, + "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAA" + ], + [ + 298, + 1, + "insert", + { + "characters": " " + }, + "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 300, + 2, + "right_delete", + null, + "AgAAAGUAAAAAAAAAZQAAAAAAAAAGAAAAaW1wb3J0ZQAAAAAAAABlAAAAAAAAAAEAAAAg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABlAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 302, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 303, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHkAAAAAAAAAegAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 304, + 1, + "right_delete", + null, + "AQAAAHoAAAAAAAAAegAAAAAAAAABAAAALg", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB6AAAAAAAAAHoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 311, + 1, + "insert", + { + "characters": "u" + }, + "AQAAAJoAAAAAAAAAmwAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 312, + 1, + "left_delete", + null, + "AQAAAJoAAAAAAAAAmgAAAAAAAAABAAAAdQ", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAAAAAAAAAJsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 313, + 1, + "insert", + { + "characters": "uration" + }, + "BwAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACcAAAAAAAAAAAAAACcAAAAAAAAAJ0AAAAAAAAAAAAAAJ0AAAAAAAAAngAAAAAAAAAAAAAAngAAAAAAAACfAAAAAAAAAAAAAACfAAAAAAAAAKAAAAAAAAAAAAAAAKAAAAAAAAAAoQAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 353, + 1, + "right_delete", + null, + "AQAAAOAFAAAAAAAA4AUAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADgBQAAAAAAAOUFAAAAAAAAAAAAAAAA8L8" + ], + [ + 359, + 1, + "right_delete", + null, + "AQAAAEYHAAAAAAAARgcAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLBwAAAAAAAEYHAAAAAAAAAAAAAAAA8L8" + ], + [ + 363, + 1, + "right_delete", + null, + "AQAAAHAHAAAAAAAAcAcAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwBwAAAAAAAHUHAAAAAAAAAAAAAAAA8L8" + ], + [ + 367, + 1, + "right_delete", + null, + "AQAAAJ4HAAAAAAAAngcAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeBwAAAAAAAKMHAAAAAAAAAAAAAAAA8L8" + ], + [ + 371, + 1, + "right_delete", + null, + "AQAAAM0HAAAAAAAAzQcAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADNBwAAAAAAANIHAAAAAAAAAAAAAAAA8L8" + ], + [ + 375, + 1, + "right_delete", + null, + "AQAAAPwHAAAAAAAA/AcAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD8BwAAAAAAAAEIAAAAAAAAAAAAAAAA8L8" + ], + [ + 379, + 1, + "right_delete", + null, + "AQAAAIcIAAAAAAAAhwgAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACMCAAAAAAAAIcIAAAAAAAAAAAAAAAA8L8" + ], + [ + 383, + 1, + "right_delete", + null, + "AQAAALYIAAAAAAAAtggAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC2CAAAAAAAALsIAAAAAAAAAAAAAAAA8L8" + ], + [ + 387, + 1, + "right_delete", + null, + "AQAAAOkIAAAAAAAA6QgAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpCAAAAAAAAO4IAAAAAAAAAAAAAAAA8L8" + ], + [ + 391, + 1, + "right_delete", + null, + "AQAAABkJAAAAAAAAGQkAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAZCQAAAAAAAB4JAAAAAAAAAAAAAAAA8L8" + ], + [ + 395, + 1, + "right_delete", + null, + "AQAAAEwJAAAAAAAATAkAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMCQAAAAAAAFEJAAAAAAAAAAAAAAAA8L8" + ], + [ + 402, + 1, + "right_delete", + null, + "AQAAAO0JAAAAAAAA7QkAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADyCQAAAAAAAO0JAAAAAAAAAAAAAAAA8L8" + ], + [ + 406, + 1, + "right_delete", + null, + "AQAAACEKAAAAAAAAIQoAAAAAAAAFAAAAY29yZS4", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAhCgAAAAAAACYKAAAAAAAAAAAAAAAA8L8" + ], + [ + 5, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABvAAAAAAAAAAQAAABjb3JlbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAABzAAAAAAAAAHQAAAAAAAAAAAAAAHQAAAAAAAAAdQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAbgAAAAAAAAAAAAAAAADwvw" + ], + [ + 16, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACTAAAAAAAAAAQAAABjb3JlkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAJgAAAAAAAAAmQAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJIAAAAAAAAAlgAAAAAAAAAAAAAAAADwvw" + ], + [ + 21, + 1, + "insert", + { + "characters": "service" + }, + "CAAAAM0AAAAAAAAAzgAAAAAAAAAAAAAAzgAAAAAAAADOAAAAAAAAAAQAAABjb3JlzgAAAAAAAADPAAAAAAAAAAAAAADPAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAA0QAAAAAAAAAAAAAA0QAAAAAAAADSAAAAAAAAAAAAAADSAAAAAAAAANMAAAAAAAAAAAAAANMAAAAAAAAA1AAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAM0AAAAAAAAA0QAAAAAAAAAAAAAAAADwvw" + ], + [ + 25, + 2, + "left_delete", + null, + "AgAAANoAAAAAAAAA2gAAAAAAAAABAAAActkAAAAAAAAA2QAAAAAAAAABAAAAZQ", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANsAAAAAAAAA2wAAAAAAAAAAAAAAAADwvw" + ], + [ + 30, + 1, + "insert", + { + "characters": "," + }, + "AQAAAIMAAAAAAAAAhAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" + ], + [ + 31, + 1, + "insert", + { + "characters": " a" + }, + "AgAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIQAAAAAAAAAhAAAAAAAAAAAAAAAAADwvw" + ], + [ + 32, + 1, + "left_delete", + null, + "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAYQ", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIYAAAAAAAAAhgAAAAAAAAAAAAAAAADwvw" + ], + [ + 33, + 1, + "insert", + { + "characters": "mailer," + }, + "BwAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAAiQAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" + ], + [ + 34, + 1, + "insert", + { + "characters": " rss" + }, + "BAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIwAAAAAAAAAjAAAAAAAAAAAAAAAAADwvw" + ], + [ + 41, + 1, + "right_delete", + null, + "AQAAAMwAAAAAAAAAzAAAAAAAAABLAAAAZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLm1haWwgaW1wb3J0IE1haWxlcgpmcm9tIHN0YWNvc3lzLmNvcmUucnNzIGltcG9ydCBSc3MK", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMwAAAAAAAAAFwEAAAAAAAAAAAAAAAAAAA" + ], + [ + 48, + 1, + "right_delete", + null, + "AQAAAOgGAAAAAAAA6AYAAAAAAAALAAAAcnNzID0gUnNzKCk", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPMGAAAAAAAA6AYAAAAAAAAAAAAAAABCQA" + ], + [ + 50, + 1, + "left_delete", + null, + "AQAAAOMGAAAAAAAA4wYAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOQGAAAAAAAA5AYAAAAAAAAAAAAAAAAAAA" + ], + [ + 54, + 1, + "right_delete", + null, + "AQAAABQIAAAAAAAAFAgAAAAAAAARAAAAbWFpbGVyID0gTWFpbGVyKCk", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAACUIAAAAAAAAFAgAAAAAAAAAAAAAAABCQA" + ], + [ + 56, + 1, + "left_delete", + null, + "AQAAAA8IAAAAAAAADwgAAAAAAAABAAAACg", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABAIAAAAAAAAEAgAAAAAAAAAAAAAAAAAAA" + ], + [ + 59, + 1, + "revert", + null, + "BAAAAAAAAAAAAAAAAAAAAAAAAADtCgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBhcmdwYXJzZQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHN5cwoKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlIGltcG9ydCBjb25maWcsIG1haWxlciwgcnNzCmZyb20gc3RhY29zeXMuc2VydmljZS5jb25maWd1cmF0aW9uIGltcG9ydCBDb25maWdQYXJhbWV0ZXIKZnJvbSBzdGFjb3N5cy5kYiBpbXBvcnQgZGF0YWJhc2UKZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwaSwgYXBwLCBmb3JtCmZyb20gc3RhY29zeXMuaW50ZXJmYWNlLndlYiBpbXBvcnQgYWRtaW4KCgojIGNvbmZpZ3VyZSBsb2dnaW5nCmRlZiBjb25maWd1cmVfbG9nZ2luZyhsZXZlbCk6CiAgICByb290X2xvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKICAgIHJvb3RfbG9nZ2VyLnNldExldmVsKGxldmVsKQogICAgaGFuZGxlciA9IGxvZ2dpbmcuU3RyZWFtSGFuZGxlcigpCiAgICBoYW5kbGVyLnNldExldmVsKGxldmVsKQogICAgZm9ybWF0dGVyID0gbG9nZ2luZy5Gb3JtYXR0ZXIoIlslKGFzY3RpbWUpc10gJShuYW1lKXMgJShsZXZlbG5hbWUpcyAlKG1lc3NhZ2UpcyIpCiAgICBoYW5kbGVyLnNldEZvcm1hdHRlcihmb3JtYXR0ZXIpCiAgICByb290X2xvZ2dlci5hZGRIYW5kbGVyKGhhbmRsZXIpCgoKZGVmIHN0YWNvc3lzX3NlcnZlcihjb25maWdfcGF0aG5hbWUpOgogICAgIyBjb25maWd1cmUgbG9nZ2luZwogICAgbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCiAgICBjb25maWd1cmVfbG9nZ2luZyhsb2dnaW5nLklORk8pCiAgICBsb2dnaW5nLmdldExvZ2dlcigid2Vya3pldWciKS5sZXZlbCA9IGxvZ2dpbmcuV0FSTklORwogICAgbG9nZ2luZy5nZXRMb2dnZXIoImFwc2NoZWR1bGVyLmV4ZWN1dG9ycyIpLmxldmVsID0gbG9nZ2luZy5XQVJOSU5HCgogICAgIyBjaGVjayBjb25maWcgZmlsZSBleGlzdHMKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShjb25maWdfcGF0aG5hbWUpOgogICAgICAgIGxvZ2dlci5lcnJvcigiQ29uZmlndXJhdGlvbiBmaWxlICclcycgbm90IGZvdW5kLiIsIGNvbmZpZ19wYXRobmFtZSkKICAgICAgICBzeXMuZXhpdCgxKQoKICAgICMgbG9hZCBhbmQgY2hlY2sgY29uZmlnCiAgICBjb25maWcubG9hZChjb25maWdfcGF0aG5hbWUpCiAgICBpc19jb25maWdfb2ssIGVycmV1cl9jb25maWcgPSBjb25maWcuY2hlY2soKQogICAgaWYgbm90IGlzX2NvbmZpZ19vazoKICAgICAgICBsb2dnZXIuZXJyb3IoIkNvbmZpZ3VyYXRpb24gaW5jb3JyZWN0ZSAnJXMnIiwgZXJyZXVyX2NvbmZpZykKICAgICAgICBzeXMuZXhpdCgxKQogICAgbG9nZ2VyLmluZm8oY29uZmlnKQoKICAgICMgY2hlY2sgZGF0YWJhc2UgZmlsZSBleGlzdHMgKHByZXZlbnRzIGZyb20gY3JlYXRpbmcgYSBmcmVzaCBkYikKICAgIGRiX3BhdGhuYW1lID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuREJfU1FMSVRFX0ZJTEUpCiAgICBpZiBub3QgZGJfcGF0aG5hbWUgb3Igbm90IG9zLnBhdGguaXNmaWxlKGRiX3BhdGhuYW1lKToKICAgICAgICBsb2dnZXIuZXJyb3IoIkRhdGFiYXNlIGZpbGUgJyVzJyBub3QgZm91bmQuIiwgZGJfcGF0aG5hbWUpCiAgICAgICAgc3lzLmV4aXQoMSkKCiAgICAjIGluaXRpYWxpemUgZGF0YWJhc2UKICAgIGRhdGFiYXNlLnNldHVwKGRiX3BhdGhuYW1lKQoKICAgIGxvZ2dlci5pbmZvKCJTdGFydCBTdGFjb3N5cyBhcHBsaWNhdGlvbiIpCgogICAgIyBnZW5lcmF0ZSBSU1MgICAgCiAgICByc3MuY29uZmlndXJlKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkxBTkcpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlJTU19GSUxFKSwKICAgICAgICBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5SU1NfUFJPVE8pLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfTkFNRSksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQogICAgcnNzLmdlbmVyYXRlKCkKCiAgICAjIGNvbmZpZ3VyZSBtYWlsZXIgICAgCiAgICBtYWlsZXIuY29uZmlndXJlKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfSE9TVCksCiAgICAgICAgY29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfUE9SVCksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU01UUF9MT0dJTiksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU01UUF9QQVNTV09SRCksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9BRE1JTl9FTUFJTCksCiAgICApCgogICAgbG9nZ2VyLmluZm8oInN0YXJ0IGludGVyZmFjZXMgJXMgJXMgJXMiLCBhcGksIGZvcm0sIGFkbWluKQoKICAgICMgc3RhcnQgRmxhc2sKICAgIGFwcC5ydW4oCiAgICAgICAgaG9zdD1jb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5IVFRQX0hPU1QpLAogICAgICAgIHBvcnQ9Y29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLkhUVFBfUE9SVCksCiAgICAgICAgZGVidWc9RmFsc2UsCiAgICAgICAgdXNlX3JlbG9hZGVyPUZhbHNlLAogICAgKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcigpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCJjb25maWciLCBoZWxwPSJjb25maWcgcGF0aCBuYW1lIikKICAgIGFyZ3MgPSBwYXJzZXIucGFyc2VfYXJncygpCiAgICBzdGFjb3N5c19zZXJ2ZXIoYXJncy5jb25maWcpCgAAAAAAAAAA5QoAAAAAAAAAAAAAAAAAAAAAAADlCgAAAAAAAAAAAAAAAAAAAAAAAOUKAAAAAAAAAAAAAA", + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAA8IAAAAAAAADwgAAAAAAAAAAAAAAADwvw" + ] + ] + }, + { + "file": "Makefile", + "settings": + { + "buffer_size": 253, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 3, + 1, + "insert", + { + "characters": "poetry" + }, + "BgAAACgAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACsAAAAAAAAAAAAAACsAAAAAAAAALAAAAAAAAAAAAAAALAAAAAAAAAAtAAAAAAAAAAAAAAAtAAAAAAAAAC4AAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAKAAAAAAAAAAoAAAAAAAAAAAAAAAAAPC/" + ], + [ + 4, + 1, + "insert", + { + "characters": " run" + }, + "BAAAAC4AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMgAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAAPC/" + ], + [ + 5, + 1, + "insert", + { + "characters": " " + }, + "AQAAADIAAAAAAAAAMwAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMgAAAAAAAAAyAAAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "insert", + { + "characters": "potr" + }, + "BAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAGYAAAAAAAAAZwAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAEJA" + ], + [ + 9, + 2, + "left_delete", + null, + "AgAAAGYAAAAAAAAAZgAAAAAAAAABAAAAcmUAAAAAAAAAZQAAAAAAAAABAAAAdA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwAAAAAAAABnAAAAAAAAAAAAAAAAAPC/" + ], + [ + 10, + 1, + "insert", + { + "characters": "etry" + }, + "BAAAAGUAAAAAAAAAZgAAAAAAAAAAAAAAZgAAAAAAAABnAAAAAAAAAAAAAABnAAAAAAAAAGgAAAAAAAAAAAAAAGgAAAAAAAAAaQAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZQAAAAAAAABlAAAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert", + { + "characters": " run" + }, + "BAAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" + ], + [ + 12, + 1, + "insert", + { + "characters": " " + }, + "AQAAAG0AAAAAAAAAbgAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbQAAAAAAAABtAAAAAAAAAAAAAAAAAPC/" + ], + [ + 15, + 1, + "insert", + { + "characters": "poetry" + }, + "BgAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 1, + "insert", + { + "characters": " run" + }, + "BAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjAAAAAAAAACMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 17, + 1, + "insert", + { + "characters": " " + }, + "AQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "paste", + null, + "AQAAAKUAAAAAAAAAsAAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAAPC/" + ], + [ + 26, + 1, + "paste", + null, + "AQAAAOAAAAAAAAAA6wAAAAAAAAAAAAAA", + "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 1, + 1, + "revert", + null, + "BgAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAYWxsOiBibGFjayB0ZXN0IHR5cGVoaW50IGxpbnQgCgpibGFjazoKCXBvZXRyeSBydW4gaXNvcnQgLS1tdWx0aS1saW5lIDMgLS1wcm9maWxlIGJsYWNrIHN0YWNvc3lzLwoJcG9ldHJ5IHJ1biBibGFjayBzdGFjb3N5cy8KCnRlc3Q6Cglwb2V0cnkgcnVuIHB5dGVzdAoKdHlwZWhpbnQ6IAoJcG9ldHJ5IHJ1biBteXB5IC0taWdub3JlLW1pc3NpbmctaW1wb3J0cyBzdGFjb3N5cy8KCmxpbnQ6Cglwb2V0cnkgcnVuIHB5bGludCBzdGFjb3N5cy8KCgAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAAAAAAAAAAAAAAAAAAAAAAP0AAAAAAAAAAAAAAAAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAAAAAAAAAAAA", + "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD7AAAAAAAAAPsAAAAAAAAAAAAAAAAA8L8" + ] + ] } ], "build_system": "", @@ -366,13 +3135,53 @@ "selected_items": [ [ - "diag", - "LSP: Goto Diagnostic in Project" + "brea", + "Python Breakpoints: Clear All" + ], + [ + "comm", + "Toggle Comment" + ], + [ + "move", + "File: Move…" + ], + [ + "break", + "Python Breakpoints: Clear All" + ], + [ + "remove", + "Package Control: Remove Package" + ], + [ + "make", + "Build With: Make" + ], + [ + "python", + "Python Breakpoints: Toggle" + ], + [ + "insta", + "Package Control: Install Package" + ], + [ + "install", + "Package Control: Install Package" ], [ "lsp", "LSP: Toggle Diagnostics Panel" ], + [ + "diag", + "LSP: Toggle Diagnostics Panel" + ], + [ + "docstr", + "AutoDocstring: Current" + ], [ "Package Control: ", "Package Control: Install Package" @@ -409,10 +3218,6 @@ "instal", "Package Control: Install Package" ], - [ - "install", - "Package Control: Install Package" - ], [ "package re", "Package Control: Remove Package" @@ -421,14 +3226,6 @@ "paka", "Package Control: Remove Package" ], - [ - "remove", - "Package Control: Remove Package" - ], - [ - "insta", - "Package Control: Install Package" - ], [ "inst", "Package Control: Install Package" @@ -459,25 +3256,50 @@ "expanded_folders": [ "/home/yannic/work/stacosys", + "/home/yannic/work/stacosys/.github", + "/home/yannic/work/stacosys/.github/workflows", + "/home/yannic/work/stacosys/docker", "/home/yannic/work/stacosys/stacosys", - "/home/yannic/work/stacosys/stacosys/conf", - "/home/yannic/work/stacosys/stacosys/core", "/home/yannic/work/stacosys/stacosys/db", "/home/yannic/work/stacosys/stacosys/interface", "/home/yannic/work/stacosys/stacosys/interface/web", "/home/yannic/work/stacosys/stacosys/model", + "/home/yannic/work/stacosys/stacosys/service", "/home/yannic/work/stacosys/tests" ], "file_history": [ - "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/sys.pyi", - "/home/yannic/work/stacosys/stacosys/conf/config.py", + "/home/yannic/work/blog/README.md", + "/home/yannic/work/blog/Dockerfile", "/usr/lib64/python3.11/logging/__init__.py", - "/home/yannic/work/stacosys/stacosys.sublime-project", - "/home/yannic/work/stacosys/flake8.ini", + "/home/yannic/work/stacosys/stacosys/core/__init__.py", + "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", + "/home/yannic/work/stacosys/stacosys/service/mail.py", + "/home/yannic/work/stacosys/stacosys/core/configuration.py", + "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", + "/home/yannic/work/stacosys/.pylintrc", + "/home/yannic/work/stacosys/stacosys/interface/form.py", + "/home/yannic/work/stacosys/tests/test_config.py", + "/home/yannic/work/stacosys/tests/test_form.py", + "/home/yannic/work/stacosys/tests/test_api.py", + "/home/yannic/work/stacosys/stacosys/core/mailer.py", + "/home/yannic/work/stacosys/stacosys/conf/config.py", + "/home/yannic/work/stacosys/stacosys/core/rss.py", + "/home/yannic/work/stacosys/stacosys/db/dao.py", + "/home/yannic/work/stacosys/stacosys/run.py", + "/home/yannic/work/stacosys/stacosys/db/database.py", "/home/yannic/work/stacosys/Makefile", - "/home/yannic/work/stacosys/stacosys/__init__.py", - "/home/yannic/work/stacosys/stacosys/core/rss.py" + "/home/yannic/work/stacosys/run.sh", + "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", + "/home/yannic/work/stacosys/stacosys/interface/__init__.py", + "/home/yannic/work/stacosys/stacosys/interface/api.py", + "/home/yannic/work/stacosys/stacosys.sublime-project", + "/home/yannic/work/stacosys/stacosys/model/comment.py", + "/home/yannic/work/stacosys/pylintrc", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/flask/app.py", + "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/sys.pyi", + "/home/yannic/work/stacosys/flake8.ini", + "/home/yannic/work/stacosys/stacosys/__init__.py" ], "find": { @@ -485,9 +3307,10 @@ }, "find_in_files": { - "height": 0.0, + "height": 104.0, "where_history": [ + "" ] }, "find_state": @@ -495,6 +3318,10 @@ "case_sensitive": false, "find_history": [ + "config", + "SITE_TOKE", + "app.conf", + "disable", "background" ], "highlight": true, @@ -519,49 +3346,32 @@ [ { "buffer": 0, - "file": "run.sh", + "file": "stacosys/interface/web/admin.py", "semi_transient": false, "settings": { - "buffer_size": 40, + "buffer_size": 2472, "regions": { + "bp-718b6604": + { + "flags": 16, + "icon": "Packages/Theme - Default/common/circle.png", + "regions": + [ + [ + 826, + 826 + ] + ], + "scope": "invalid" + } }, "selection": [ [ - 27, - 27 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/run.sh", - "syntax": "Packages/ShellScript/Bash.sublime-syntax" - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 7, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 1, - "file": "stacosys/run.py", - "semi_transient": false, - "settings": - { - "buffer_size": 3298, - "regions": - { - }, - "selection": - [ - [ - 1918, - 1918 + 1207, + 1207 ] ], "settings": @@ -583,46 +3393,14 @@ } ], "lsp_active": true, - "lsp_hover_provider_count": 10, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", + "lsp_hover_provider_count": 8, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/interface/web/admin.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 964.0, - "zoom_level": 1.0 - }, - "stack_index": 6, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 2, - "file": "Makefile", - "semi_transient": false, - "settings": - { - "buffer_size": 533, - "regions": - { - }, - "selection": - [ - [ - 334, - 334 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", - "syntax": "Packages/Makefile/Makefile.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": false - }, - "translation.x": 0.0, "translation.y": 0.0, "zoom_level": 1.0 }, @@ -631,21 +3409,20 @@ "type": "text" }, { - "buffer": 3, - "file": "stacosys/core/mailer.py", - "selected": true, + "buffer": 1, + "file": "tests/test_config.py", "semi_transient": false, "settings": { - "buffer_size": 1087, + "buffer_size": 1382, "regions": { }, "selection": [ [ - 1086, - 1086 + 1222, + 1222 ] ], "settings": @@ -667,97 +3444,14 @@ } ], "lsp_active": true, - "lsp_hover_provider_count": 10, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/core/mailer.py", + "lsp_hover_provider_count": 4, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_config.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 399.0, - "zoom_level": 1.0 - }, - "stack_index": 0, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 4, - "file": "stacosys/db/dao.py", - "semi_transient": true, - "settings": - { - "buffer_size": 1633, - "regions": - { - }, - "selection": - [ - [ - 524, - 524 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/dao.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 3, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 5, - "file": "stacosys.sublime-project", - "semi_transient": false, - "settings": - { - "buffer_size": 381, - "regions": - { - }, - "selection": - [ - [ - 364, - 364 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys.sublime-project", - "syntax": "Packages/JSON/JSON.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, "translation.y": 0.0, "zoom_level": 1.0 }, @@ -766,63 +3460,43 @@ "type": "text" }, { - "buffer": 6, - "file": "stacosys/model/comment.py", - "semi_transient": false, + "buffer": 2, + "file": "Dockerfile", + "selected": true, + "semi_transient": true, "settings": { - "buffer_size": 471, + "buffer_size": 716, "regions": { }, "selection": [ [ - 150, - 150 + 0, + 0 ] ], "settings": { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 6, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/model/comment.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true + "lsp_uri": "file:///home/yannic/work/stacosys/Dockerfile", + "syntax": "Packages/Text/Plain text.tmLanguage" }, "translation.x": 0.0, "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 4, + "stack_index": 0, "stack_multiselect": false, "type": "text" }, { - "buffer": 7, - "file": "stacosys/conf/config.py", + "buffer": 3, + "file": "tests/test_api.py", "semi_transient": false, "settings": { - "buffer_size": 2481, + "buffer_size": 1634, "regions": { }, @@ -852,20 +3526,203 @@ } ], "lsp_active": true, - "lsp_hover_provider_count": 6, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/conf/config.py", + "lsp_hover_provider_count": 4, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 1539.0, + "translation.y": 114.0, + "zoom_level": 1.0 + }, + "stack_index": 3, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 4, + "file": "stacosys/service/__init__.py", + "semi_transient": false, + "settings": + { + "buffer_size": 180, + "regions": + { + }, + "selection": + [ + [ + 149, + 149 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 5, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/__init__.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 4, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 5, + "file": "stacosys/interface/form.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2659, + "regions": + { + }, + "selection": + [ + [ + 2356, + 2356 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 6, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/interface/form.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 1311.0, "zoom_level": 1.0 }, "stack_index": 5, "stack_multiselect": false, "type": "text" + }, + { + "buffer": 6, + "file": "stacosys/run.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2789, + "regions": + { + }, + "selection": + [ + [ + 2063, + 2063 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 6, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 6, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 7, + "file": "Makefile", + "semi_transient": false, + "settings": + { + "buffer_size": 253, + "regions": + { + }, + "selection": + [ + [ + 251, + 251 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", + "syntax": "Packages/Makefile/Makefile.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": false + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 7, + "stack_multiselect": false, + "type": "text" } ] } @@ -876,7 +3733,7 @@ }, "input": { - "height": 0.0 + "height": 40.0 }, "layout": { @@ -909,6 +3766,10 @@ { "height": 261.0 }, + "output.exec": + { + "height": 132.0 + }, "output.find_results": { "height": 0.0 @@ -943,12 +3804,12 @@ }, "select_project": { - "height": 0.0, + "height": 500.0, "last_filter": "", "selected_items": [ ], - "width": 0.0 + "width": 380.0 }, "select_symbol": { @@ -967,7 +3828,7 @@ "show_open_files": false, "show_tabs": true, "side_bar_visible": true, - "side_bar_width": 311.0, + "side_bar_width": 302.0, "status_bar_visible": true, "template_settings": { diff --git a/stacosys/core/rss.py b/stacosys/core/rss.py deleted file mode 100644 index 42f9b8d..0000000 --- a/stacosys/core/rss.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from datetime import datetime - -import markdown -import PyRSS2Gen - -from stacosys.model.comment import Comment - - -class Rss: - def __init__( - self, - lang, - rss_file, - rss_proto, - site_name, - site_url, - ): - self._lang = lang - self._rss_file = rss_file - self._rss_proto = rss_proto - self._site_name = site_name - self._site_url = site_url - - def generate(self): - md = markdown.Markdown() - - items = [] - for row in ( - Comment.select() - .where(Comment.published) - .order_by(-Comment.published) - .limit(10) - ): - item_link = "%s://%s%s" % ( - self._rss_proto, - self._site_url, - row.url, - ) - items.append( - PyRSS2Gen.RSSItem( - title="%s - %s://%s%s" - % ( - self._rss_proto, - row.author_name, - self._site_url, - row.url, - ), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), - pubDate=row.published, - ) - ) - - rss_title = 'Commentaires du site "%s"' % self._site_name - rss = PyRSS2Gen.RSS2( - title=rss_title, - link="%s://%s" % (self._rss_proto, self._site_url), - description=rss_title, - lastBuildDate=datetime.now(), - items=items, - ) - rss.write_xml(open(self._rss_file, "w"), encoding="utf-8") diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 04b52b3..52b5bb8 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -7,6 +7,8 @@ from flask import abort, redirect, request from stacosys.db import dao from stacosys.interface import app +from stacosys.service import config, mailer +from stacosys.service.configuration import ConfigParameter logger = logging.getLogger(__name__) @@ -46,7 +48,7 @@ def new_form_comment(): # send notification e-mail asynchronously submit_new_comment(comment) - return redirect(app.config.get("SITE_REDIRECT"), code=302) + return redirect(config.get(ConfigParameter.SITE_REDIRECT), code=302) def check_form_data(posted_comment): @@ -57,7 +59,7 @@ def check_form_data(posted_comment): @background.task def submit_new_comment(comment): - site_url = app.config.get("SITE_URL") + site_url = config.get(ConfigParameter.SITE_URL) comment_list = ( f"Web admin interface: {site_url}/web/admin", "", @@ -72,8 +74,9 @@ def submit_new_comment(comment): email_body = "\n".join(comment_list) # send email to notify admin - subject = "STACOSYS " + app.config.get("SITE_NAME") - if app.config.get("MAILER").send(subject, email_body): + site_name = config.get(ConfigParameter.SITE_NAME) + subject = f"STACOSYS {site_name}" + if mailer.send(subject, email_body): logger.debug("new comment processed") # save notification datetime diff --git a/stacosys/interface/web/admin.py b/stacosys/interface/web/admin.py index 26d172f..8d81e3c 100644 --- a/stacosys/interface/web/admin.py +++ b/stacosys/interface/web/admin.py @@ -8,6 +8,8 @@ from flask import flash, redirect, render_template, request, session from stacosys.db import dao from stacosys.interface import app +from stacosys.service import config, rss +from stacosys.service.configuration import ConfigParameter logger = logging.getLogger(__name__) @@ -23,8 +25,8 @@ def index(): def is_login_ok(username, password): hashed = hashlib.sha256(password.encode()).hexdigest().upper() return ( - app.config.get("WEB_USERNAME") == username - and app.config.get("WEB_PASSWORD") == hashed + config.get(ConfigParameter.WEB_USERNAME) == username + and config.get(ConfigParameter.WEB_PASSWORD) == hashed ) @@ -40,7 +42,7 @@ def login(): flash("Identifiant ou mot de passe incorrect") return redirect("/web/login") # GET - return render_template("login_" + app.config.get("LANG", "fr") + ".html") + return render_template("login_" + config.get(ConfigParameter.LANG) + ".html") @app.route("/web/logout", methods=["GET"]) @@ -51,16 +53,19 @@ def logout(): @app.route("/web/admin", methods=["GET"]) def admin_homepage(): - if not ("user" in session and session["user"] == app.config.get("WEB_USERNAME")): + if not ( + "user" in session + and session["user"] == config.get(ConfigParameter.WEB_USERNAME) + ): # TODO localization flash("Vous avez été déconnecté.") return redirect("/web/login") comments = dao.find_not_published_comments() return render_template( - "admin_" + app.config.get("LANG", "fr") + ".html", + "admin_" + config.get(ConfigParameter.LANG) + ".html", comments=comments, - baseurl=app.config.get("SITE_URL"), + baseurl=config.get(ConfigParameter.SITE_URL), ) @@ -72,7 +77,7 @@ def admin_action(): flash("Commentaire introuvable") elif request.form.get("action") == "APPROVE": dao.publish_comment(comment) - app.config.get("RSS").generate() + rss.generate() # TODO localization flash("Commentaire publié") else: diff --git a/stacosys/run.py b/stacosys/run.py index 197215b..5109299 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -6,12 +6,11 @@ import logging import os import sys -from stacosys.conf.config import Config, ConfigParameter -from stacosys.core.mailer import Mailer -from stacosys.core.rss import Rss from stacosys.db import database from stacosys.interface import api, app, form from stacosys.interface.web import admin +from stacosys.service import config, mailer, rss +from stacosys.service.configuration import ConfigParameter # configure logging @@ -37,16 +36,16 @@ def stacosys_server(config_pathname): logger.error("Configuration file '%s' not found.", config_pathname) sys.exit(1) - # load config - conf = Config.load(config_pathname) - is_config_ok, erreur_config = conf.check() + # load and check config + config.load(config_pathname) + is_config_ok, erreur_config = config.check() if not is_config_ok: logger.error("Configuration incorrecte '%s'", erreur_config) sys.exit(1) - logger.info(conf) + logger.info(config) # check database file exists (prevents from creating a fresh db) - db_pathname = conf.get(ConfigParameter.DB_SQLITE_FILE) + db_pathname = config.get(ConfigParameter.DB_SQLITE_FILE) if not db_pathname or not os.path.isfile(db_pathname): logger.error("Database file '%s' not found.", db_pathname) sys.exit(1) @@ -57,39 +56,29 @@ def stacosys_server(config_pathname): logger.info("Start Stacosys application") # generate RSS - rss = Rss( - conf.get(ConfigParameter.LANG), - conf.get(ConfigParameter.RSS_FILE), - conf.get(ConfigParameter.RSS_PROTO), - conf.get(ConfigParameter.SITE_NAME), - conf.get(ConfigParameter.SITE_URL), + rss.configure( + config.get(ConfigParameter.RSS_FILE), + config.get(ConfigParameter.SITE_PROTO), + config.get(ConfigParameter.SITE_NAME), + config.get(ConfigParameter.SITE_URL), ) rss.generate() # configure mailer - mailer = Mailer( - conf.get(ConfigParameter.SMTP_HOST), - conf.get_int(ConfigParameter.SMTP_PORT), - conf.get(ConfigParameter.SMTP_LOGIN), - conf.get(ConfigParameter.SMTP_PASSWORD), - conf.get(ConfigParameter.SITE_ADMIN_EMAIL), + mailer.configure_smtp( + config.get(ConfigParameter.SMTP_HOST), + config.get_int(ConfigParameter.SMTP_PORT), + config.get(ConfigParameter.SMTP_LOGIN), + config.get(ConfigParameter.SMTP_PASSWORD), ) + mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) - # inject config parameters into flask - app.config.update(LANG=conf.get(ConfigParameter.LANG)) - app.config.update(SITE_NAME=conf.get(ConfigParameter.SITE_NAME)) - app.config.update(SITE_URL=conf.get(ConfigParameter.SITE_URL)) - app.config.update(SITE_REDIRECT=conf.get(ConfigParameter.SITE_REDIRECT)) - app.config.update(WEB_USERNAME=conf.get(ConfigParameter.WEB_USERNAME)) - app.config.update(WEB_PASSWORD=conf.get(ConfigParameter.WEB_PASSWORD)) - app.config.update(MAILER=mailer) - app.config.update(RSS=rss) logger.info("start interfaces %s %s %s", api, form, admin) # start Flask app.run( - host=conf.get(ConfigParameter.HTTP_HOST), - port=conf.get_int(ConfigParameter.HTTP_PORT), + host=config.get(ConfigParameter.HTTP_HOST), + port=config.get_int(ConfigParameter.HTTP_PORT), debug=False, use_reloader=False, ) diff --git a/stacosys/service/__init__.py b/stacosys/service/__init__.py new file mode 100644 index 0000000..6fcc80a --- /dev/null +++ b/stacosys/service/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .configuration import Config +from .mail import Mailer +from .rssfeed import Rss + +config = Config() +mailer = Mailer() +rss = Rss() diff --git a/stacosys/conf/config.py b/stacosys/service/configuration.py similarity index 76% rename from stacosys/conf/config.py rename to stacosys/service/configuration.py index 78adea8..85b3db8 100644 --- a/stacosys/conf/config.py +++ b/stacosys/service/configuration.py @@ -12,7 +12,6 @@ class ConfigParameter(Enum): HTTP_HOST = "http.host" HTTP_PORT = "http.port" - RSS_PROTO = "rss.proto" RSS_FILE = "rss.file" SMTP_HOST = "smtp.host" @@ -20,6 +19,7 @@ class ConfigParameter(Enum): SMTP_LOGIN = "smtp.login" SMTP_PASSWORD = "smtp.password" + SITE_PROTO = "site.proto" SITE_NAME = "site.name" SITE_URL = "site.url" SITE_ADMIN_EMAIL = "site.admin_email" @@ -30,14 +30,16 @@ class ConfigParameter(Enum): class Config: - def __init__(self): - self._cfg = configparser.ConfigParser() - @classmethod - def load(cls, config_pathname): - config = cls() - config._cfg.read(config_pathname) - return config + _cfg = configparser.ConfigParser() + + # def __new__(cls): + # if not hasattr(cls, "instance"): + # cls.instance = super(Config, cls).__new__(cls) + # return cls.instance + + def load(self, config_pathname): + self._cfg.read(config_pathname) def _split_key(self, key: ConfigParameter): section, param = str(key.value).split(".") @@ -50,12 +52,12 @@ class Config: section, param = self._split_key(key) return self._cfg.has_option(section, param) - def get(self, key: ConfigParameter): + def get(self, key: ConfigParameter) -> str: section, param = self._split_key(key) return ( self._cfg.get(section, param) if self._cfg.has_option(section, param) - else None + else "" ) def put(self, key: ConfigParameter, value): @@ -64,11 +66,11 @@ class Config: self._cfg.add_section(section) self._cfg.set(section, param, str(value)) - def get_int(self, key: ConfigParameter): + def get_int(self, key: ConfigParameter) -> int: value = self.get(key) return int(value) if value else 0 - def get_bool(self, key: ConfigParameter): + def get_bool(self, key: ConfigParameter) -> bool: value = self.get(key) assert value in ( "yes", @@ -85,8 +87,8 @@ class Config: return (True, None) def __repr__(self): - d = dict() + dict_repr = {} for section in self._cfg.sections(): for option in self._cfg.options(section): - d[".".join([section, option])] = self._cfg.get(section, option) - return str(d) + dict_repr[".".join([section, option])] = self._cfg.get(section, option) + return str(dict_repr) diff --git a/stacosys/core/mailer.py b/stacosys/service/mail.py similarity index 71% rename from stacosys/core/mailer.py rename to stacosys/service/mail.py index 43ec706..6d756bb 100644 --- a/stacosys/core/mailer.py +++ b/stacosys/service/mail.py @@ -10,21 +10,29 @@ logger = logging.getLogger(__name__) class Mailer: - def __init__( + def __init__(self) -> None: + self._smtp_host: str = "" + self._smtp_port: int = 0 + self._smtp_login: str = "" + self._smtp_password: str = "" + self._site_admin_email: str = "" + + def configure_smtp( self, smtp_host, smtp_port, smtp_login, smtp_password, - site_admin_email, - ): + ) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_login = smtp_login self._smtp_password = smtp_password + + def configure_destination(self, site_admin_email) -> None: self._site_admin_email = site_admin_email - def send(self, subject, message): + def send(self, subject, message) -> bool: sender = self._smtp_login receivers = [self._site_admin_email] @@ -34,7 +42,7 @@ class Mailer: msg["From"] = sender context = ssl.create_default_context() - # TODO catch SMTP failure + # TODO catch SMTP failure with smtplib.SMTP_SSL( self._smtp_host, self._smtp_port, context=context ) as server: diff --git a/stacosys/service/rssfeed.py b/stacosys/service/rssfeed.py new file mode 100644 index 0000000..a5f5c12 --- /dev/null +++ b/stacosys/service/rssfeed.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from datetime import datetime + +import markdown +import PyRSS2Gen + +from stacosys.model.comment import Comment + + +class Rss: + def __init__(self) -> None: + self._rss_file: str = "" + self._site_proto: str = "" + self._site_name: str = "" + self._site_url: str = "" + + def configure( + self, + rss_file, + site_proto, + site_name, + site_url, + ) -> None: + self._rss_file = rss_file + self._site_proto = site_proto + self._site_name = site_name + self._site_url = site_url + + def generate(self) -> None: + markdownizer = markdown.Markdown() + + items = [] + for row in ( + Comment.select() + .where(Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = f"{self._site_proto}://{self._site_url}{row.url}" + items.append( + PyRSS2Gen.RSSItem( + title=f"{self._site_proto}://{self._site_url}{row.url} - {row.author_name}", + link=item_link, + description=markdownizer.convert(row.content), + guid=PyRSS2Gen.Guid(f"{item_link}{row.id}"), + pubDate=row.published, + ) + ) + + rss_title = f"Commentaires du site {self._site_name}" + rss = PyRSS2Gen.RSS2( + title=rss_title, + link=f"{self._site_proto}://{self._site_url}", + description=rss_title, + lastBuildDate=datetime.now(), + items=items, + ) + # TODO technical debt: replace pyRss2Gen + # TODO validate feed (https://validator.w3.org/feed/check.cgi) + # pylint: disable=consider-using-with + rss.write_xml(open(self._rss_file, "w", encoding="utf-8"), encoding="utf-8") diff --git a/tests/test_api.py b/tests/test_api.py index 6cb2196..8efd052 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -25,7 +25,6 @@ def client(): logger = logging.getLogger(__name__) database.setup(":memory:") init_test_db() - app.config.update(SITE_TOKEN="ETC") logger.info(f"start interface {api}") return app.test_client() diff --git a/tests/test_config.py b/tests/test_config.py index e098965..3aaaaff 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,7 +3,8 @@ import unittest -from stacosys.conf.config import Config, ConfigParameter +from stacosys.service import config +from stacosys.service.configuration import ConfigParameter EXPECTED_DB_SQLITE_FILE = "db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -11,31 +12,30 @@ EXPECTED_LANG = "fr" class ConfigTestCase(unittest.TestCase): - def setUp(self): - self.conf = Config() - self.conf.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) - self.conf.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + def setUp(self): + config.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) + config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) def test_exists(self): - self.assertTrue(self.conf.exists(ConfigParameter.DB_SQLITE_FILE)) + self.assertTrue(config.exists(ConfigParameter.DB_SQLITE_FILE)) def test_get(self): self.assertEqual( - self.conf.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE + config.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE ) - self.assertIsNone(self.conf.get(ConfigParameter.HTTP_HOST)) + self.assertEqual(config.get(ConfigParameter.HTTP_HOST), "") self.assertEqual( - self.conf.get(ConfigParameter.HTTP_PORT), str(EXPECTED_HTTP_PORT) + config.get(ConfigParameter.HTTP_PORT), str(EXPECTED_HTTP_PORT) ) - self.assertEqual(self.conf.get_int(ConfigParameter.HTTP_PORT), 8080) + self.assertEqual(config.get_int(ConfigParameter.HTTP_PORT), 8080) try: - self.conf.get_bool(ConfigParameter.DB_SQLITE_FILE) + config.get_bool(ConfigParameter.DB_SQLITE_FILE) self.assertTrue(False) except AssertionError: pass def test_put(self): - self.assertFalse(self.conf.exists(ConfigParameter.LANG)) - self.conf.put(ConfigParameter.LANG, EXPECTED_LANG) - self.assertTrue(self.conf.exists(ConfigParameter.LANG)) - self.assertEqual(self.conf.get(ConfigParameter.LANG), EXPECTED_LANG) + self.assertFalse(config.exists(ConfigParameter.LANG)) + config.put(ConfigParameter.LANG, EXPECTED_LANG) + self.assertTrue(config.exists(ConfigParameter.LANG)) + self.assertEqual(config.get(ConfigParameter.LANG), EXPECTED_LANG) diff --git a/tests/test_form.py b/tests/test_form.py index 55ebda8..dedef3a 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -13,8 +13,7 @@ from stacosys.interface import form @pytest.fixture def client(): logger = logging.getLogger(__name__) - database.setup(":memory:") - app.config.update(SITE_REDIRECT="/redirect") + database.setup(":memory:") logger.info(f"start interface {form}") return app.test_client() From a62cb8eff1f688272b418aace52bc2e36f408957 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 27 Nov 2022 20:43:06 +0100 Subject: [PATCH 480/586] replace unittest by pytest --- tests/test_config.py | 45 +++++++++------------ tests/test_db.py | 96 ++++++++++++++++++++++---------------------- 2 files changed, 68 insertions(+), 73 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 3aaaaff..d0d8e75 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import unittest +import pytest from stacosys.service import config from stacosys.service.configuration import ConfigParameter @@ -11,31 +11,24 @@ EXPECTED_HTTP_PORT = 8080 EXPECTED_LANG = "fr" -class ConfigTestCase(unittest.TestCase): - def setUp(self): - config.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) - config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) +@pytest.fixture +def init_config(): + config.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) + config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) - def test_exists(self): - self.assertTrue(config.exists(ConfigParameter.DB_SQLITE_FILE)) +def test_exists(init_config): + assert config.exists(ConfigParameter.DB_SQLITE_FILE) - def test_get(self): - self.assertEqual( - config.get(ConfigParameter.DB_SQLITE_FILE), EXPECTED_DB_SQLITE_FILE - ) - self.assertEqual(config.get(ConfigParameter.HTTP_HOST), "") - self.assertEqual( - config.get(ConfigParameter.HTTP_PORT), str(EXPECTED_HTTP_PORT) - ) - self.assertEqual(config.get_int(ConfigParameter.HTTP_PORT), 8080) - try: - config.get_bool(ConfigParameter.DB_SQLITE_FILE) - self.assertTrue(False) - except AssertionError: - pass +def test_get(init_config): + assert config.get(ConfigParameter.DB_SQLITE_FILE) == EXPECTED_DB_SQLITE_FILE + assert config.get(ConfigParameter.HTTP_HOST) == "" + assert config.get(ConfigParameter.HTTP_PORT) == str(EXPECTED_HTTP_PORT) + assert config.get_int(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT + with pytest.raises(AssertionError): + config.get_bool(ConfigParameter.DB_SQLITE_FILE) - def test_put(self): - self.assertFalse(config.exists(ConfigParameter.LANG)) - config.put(ConfigParameter.LANG, EXPECTED_LANG) - self.assertTrue(config.exists(ConfigParameter.LANG)) - self.assertEqual(config.get(ConfigParameter.LANG), EXPECTED_LANG) +def test_put(init_config): + assert not config.exists(ConfigParameter.LANG) + config.put(ConfigParameter.LANG, EXPECTED_LANG) + assert config.exists(ConfigParameter.LANG) + assert config.get(ConfigParameter.LANG) == EXPECTED_LANG diff --git a/tests/test_db.py b/tests/test_db.py index 99eeb7e..1f390c3 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,53 +1,55 @@ -import unittest +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest from stacosys.db import dao from stacosys.db import database - -class DbTestCase(unittest.TestCase): - def setUp(self): - database.setup(":memory:") - - def test_dao_published(self): - - # test count published - self.assertEqual(0, dao.count_published_comments("")) - c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") - self.assertEqual(0, dao.count_published_comments("")) - dao.publish_comment(c1) - self.assertEqual(1, dao.count_published_comments("")) - c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") - dao.publish_comment(c2) - self.assertEqual(2, dao.count_published_comments("")) - c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") - dao.publish_comment(c3) - self.assertEqual(1, dao.count_published_comments("/post1")) - self.assertEqual(2, dao.count_published_comments("/post2")) - - # test find published - self.assertEqual(0, len(dao.find_published_comments_by_url("/"))) - self.assertEqual(1, len(dao.find_published_comments_by_url("/post1"))) - self.assertEqual(2, len(dao.find_published_comments_by_url("/post2"))) - - dao.delete_comment(c1) - self.assertEqual(0, len(dao.find_published_comments_by_url("/post1"))) - - def test_dao_notified(self): - - # test count notified - self.assertEqual(0, len(dao.find_not_notified_comments())) - c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") - self.assertEqual(1, len(dao.find_not_notified_comments())) - c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") - self.assertEqual(2, len(dao.find_not_notified_comments())) - dao.notify_comment(c1) - dao.notify_comment(c2) - self.assertEqual(0, len(dao.find_not_notified_comments())) - c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") - self.assertEqual(1, len(dao.find_not_notified_comments())) - dao.notify_comment(c3) - self.assertEqual(0, len(dao.find_not_notified_comments())) +@pytest.fixture +def setup_db(): + database.setup(":memory:") + + + +def test_dao_published(setup_db): + + # test count published + assert 0 == dao.count_published_comments("") + c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") + assert 0 == dao.count_published_comments("") + dao.publish_comment(c1) + assert 1 == dao.count_published_comments("") + c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") + dao.publish_comment(c2) + assert 2 == dao.count_published_comments("") + c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") + dao.publish_comment(c3) + assert 1 == dao.count_published_comments("/post1") + assert 2 == dao.count_published_comments("/post2") + + # test find published + assert 0 == len(dao.find_published_comments_by_url("/")) + assert 1 == len(dao.find_published_comments_by_url("/post1")) + assert 2 == len(dao.find_published_comments_by_url("/post2")) + + dao.delete_comment(c1) + assert 0 == len(dao.find_published_comments_by_url("/post1")) + +def test_dao_notified(setup_db): + + # test count notified + assert 0 == len(dao.find_not_notified_comments()) + c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") + assert 1 == len(dao.find_not_notified_comments()) + c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") + assert 2 == len(dao.find_not_notified_comments()) + dao.notify_comment(c1) + dao.notify_comment(c2) + assert 0 == len(dao.find_not_notified_comments()) + c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") + assert 1 == len(dao.find_not_notified_comments()) + dao.notify_comment(c3) + assert 0 == len(dao.find_not_notified_comments()) -if __name__ == "__main__": - unittest.main() From 15413672c9b62165f9770fc424148f63264d8788 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:51:34 +0100 Subject: [PATCH 481/586] Add test coverage to local makefile --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 16fd38e..2eb9197 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,9 @@ black: poetry run isort --multi-line 3 --profile black stacosys/ poetry run black stacosys/ -test: - poetry run pytest +test: + poetry run coverage run -m --source=stacosys pytest + poetry run coverage report typehint: poetry run mypy --ignore-missing-imports stacosys/ From 601259cc557851b01c4428762e828a1ddb57d512 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:52:10 +0100 Subject: [PATCH 482/586] update deps and remove unused ones --- poetry.lock | 390 +++++++++++-------------------------------------- pyproject.toml | 6 +- tox.ini | 9 -- 3 files changed, 86 insertions(+), 319 deletions(-) delete mode 100644 tox.ini diff --git a/poetry.lock b/poetry.lock index 2842c27..c5a9026 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,33 +1,6 @@ -[[package]] -name = "apscheduler" -version = "3.9.1.post1" -description = "In-process task scheduler with Cron-like capabilities" -category = "main" -optional = false -python-versions = "!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.dependencies] -pytz = "*" -setuptools = ">=0.7" -six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" - -[package.extras] -asyncio = ["trollius"] -doc = ["sphinx", "sphinx-rtd-theme"] -gevent = ["gevent"] -mongodb = ["pymongo (>=3.0)"] -redis = ["redis (>=3.0)"] -rethinkdb = ["rethinkdb (>=2.4.0)"] -sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["mock", "pytest", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] -tornado = ["tornado (>=4.3)"] -twisted = ["twisted"] -zookeeper = ["kazoo"] - [[package]] name = "astroid" -version = "2.12.12" +version = "2.12.13" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -119,17 +92,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 [[package]] name = "coverage" -version = "5.5" +version = "6.5.0" description = "Code coverage measurement for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.dependencies] -toml = {version = "*", optional = true, markers = "extra == \"toml\""} +python-versions = ">=3.7" [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "coveralls" @@ -158,14 +128,6 @@ python-versions = ">=3.7" [package.extras] graph = ["objgraph (>=1.7.2)"] -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "docopt" version = "0.6.2" @@ -174,34 +136,22 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "filelock" -version = "3.8.0" -description = "A platform independent file lock." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] - [[package]] name = "flake8" -version = "5.0.4" +version = "6.0.0" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8.1" [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" +pycodestyle = ">=2.10.0,<2.11.0" +pyflakes = ">=3.0.0,<3.1.0" [[package]] name = "flake8-black" -version = "0.3.4" +version = "0.3.5" description = "flake8 plugin to call black as a code style validator" category = "dev" optional = false @@ -350,7 +300,7 @@ python-versions = "*" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -377,7 +327,7 @@ python-versions = "*" name = "platformdirs" version = "2.5.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -389,7 +339,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -397,17 +347,9 @@ python-versions = ">=3.6" dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "pycodestyle" -version = "2.9.1" +version = "2.10.0" description = "Python style guide checker" category = "dev" optional = false @@ -415,7 +357,7 @@ python-versions = ">=3.6" [[package]] name = "pyflakes" -version = "2.5.0" +version = "3.0.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -423,14 +365,14 @@ python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.15.6" +version = "2.15.7" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.7.2" [package.dependencies] -astroid = ">=2.12.12,<=2.14.0-dev0" +astroid = ">=2.12.13,<=2.14.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = ">=0.2" isort = ">=4.2.5,<6" @@ -446,7 +388,7 @@ testutils = ["gitpython (>3)"] name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" +category = "dev" optional = false python-versions = ">=3.6.8" @@ -494,25 +436,6 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] -[[package]] -name = "pytz" -version = "2022.6" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pytz-deprecation-shim" -version = "0.1.0.post0" -description = "Shims to make deprecation of pytz easier" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" - -[package.dependencies] -tzdata = {version = "*", markers = "python_version >= \"3.6\""} - [[package]] name = "requests" version = "2.28.1" @@ -531,35 +454,6 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "setuptools" -version = "65.6.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" version = "2.0.1" @@ -576,27 +470,6 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "tox" -version = "3.27.1" -description = "tox is a generic virtualenv management and test command line tool" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] - [[package]] name = "types-markdown" version = "3.4.2.1" @@ -613,60 +486,19 @@ category = "dev" optional = false python-versions = ">=3.7" -[[package]] -name = "tzdata" -version = "2022.6" -description = "Provider of IANA time zone data" -category = "main" -optional = false -python-versions = ">=2" - -[[package]] -name = "tzlocal" -version = "4.2" -description = "tzinfo object for the local timezone" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pytz-deprecation-shim = "*" -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] -test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] - [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "virtualenv" -version = "20.16.7" -description = "Virtual Python Environment builder" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - [[package]] name = "werkzeug" version = "2.2.2" @@ -692,16 +524,12 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "~3.11" -content-hash = "94614b000f5848489ed0e32caf25a06df9daa05bc063d0e2fe03a00f6859d27b" +content-hash = "c7fd5e51d22b64ab20394250b12819609483e7e825996bb8c62e6bb2bb537951" [metadata.files] -apscheduler = [ - {file = "APScheduler-3.9.1.post1-py2.py3-none-any.whl", hash = "sha256:c8c618241dbb2785ed5a687504b14cb1851d6f7b5a4edf3a51e39cc6a069967a"}, - {file = "APScheduler-3.9.1.post1.tar.gz", hash = "sha256:b2bea0309569da53a7261bfa0ce19c67ddbfe151bda776a6a907579fdbd3eb2a"}, -] astroid = [ - {file = "astroid-2.12.12-py3-none-any.whl", hash = "sha256:72702205200b2a638358369d90c222d74ebc376787af8fb2f7f2a86f7b5cc85f"}, - {file = "astroid-2.12.12.tar.gz", hash = "sha256:1c00a14f5a3ed0339d38d2e2e5b74ea2591df5861c0936bb292b84ccf3a78d83"}, + {file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"}, + {file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"}, ] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, @@ -751,58 +579,56 @@ colorama = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] coveralls = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, @@ -812,24 +638,16 @@ dill = [ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, ] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] -filelock = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, -] flake8 = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, ] flake8-black = [ - {file = "flake8-black-0.3.4.tar.gz", hash = "sha256:7f96a4c80a828d09f1d550724e16aabb2adacd6a5f8e0bb051df422fc63d2183"}, - {file = "flake8_black-0.3.4-py3-none-any.whl", hash = "sha256:fb52f258dfa6a25645c4ba8730eadc5f2ecd32057bf6c9fc21aef1cba9fefd74"}, + {file = "flake8-black-0.3.5.tar.gz", hash = "sha256:9e93252b1314a8eb3c2f55dec54a07239e502b12f57567f2c105f2202714b15e"}, + {file = "flake8_black-0.3.5-py3-none-any.whl", hash = "sha256:4948a579fdddd98fbf935fd94255dfcfce560c4ddc1ceee08e3f12d6114c8619"}, ] flask = [ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, @@ -981,21 +799,17 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] pycodestyle = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, ] pylint = [ - {file = "pylint-2.15.6-py3-none-any.whl", hash = "sha256:15060cc22ed6830a4049cf40bc24977744df2e554d38da1b2657591de5bcd052"}, - {file = "pylint-2.15.6.tar.gz", hash = "sha256:25b13ddcf5af7d112cf96935e21806c1da60e676f952efb650130f2a4483421c"}, + {file = "pylint-2.15.7-py3-none-any.whl", hash = "sha256:1d561d1d3e8be9dd880edc685162fbdaa0409c88b9b7400873c0cf345602e326"}, + {file = "pylint-2.15.7.tar.gz", hash = "sha256:91e4776dbcb4b4d921a3e4b6fec669551107ba11f29d9199154a01622e460a57"}, ] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, @@ -1012,30 +826,10 @@ pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] -pytz = [ - {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, - {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, -] -pytz-deprecation-shim = [ - {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, - {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, -] requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] -setuptools = [ - {file = "setuptools-65.6.0-py3-none-any.whl", hash = "sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840"}, - {file = "setuptools-65.6.0.tar.gz", hash = "sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1044,10 +838,6 @@ tomlkit = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, ] -tox = [ - {file = "tox-3.27.1-py2.py3-none-any.whl", hash = "sha256:f52ca66eae115fcfef0e77ef81fd107133d295c97c52df337adedb8dfac6ab84"}, - {file = "tox-3.27.1.tar.gz", hash = "sha256:b2a920e35a668cc06942ffd1cf3a4fb221a4d909ca72191fb6d84b0b18a7be04"}, -] types-markdown = [ {file = "types-Markdown-3.4.2.1.tar.gz", hash = "sha256:03c0904cf5886a7d8193e2f50bcf842afc89e0ab80f060f389f6c2635c65628f"}, {file = "types_Markdown-3.4.2.1-py3-none-any.whl", hash = "sha256:b2333f6f4b8f69af83de359e10a097e4a3f14bbd6d2484e1829d9b0ec56fa0cb"}, @@ -1056,21 +846,9 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -tzdata = [ - {file = "tzdata-2022.6-py2.py3-none-any.whl", hash = "sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342"}, - {file = "tzdata-2022.6.tar.gz", hash = "sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae"}, -] -tzlocal = [ - {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, - {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, -] urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] -virtualenv = [ - {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, - {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, diff --git a/pyproject.toml b/pyproject.toml index d1ec3da..f048766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,19 +8,17 @@ include = ["run.py"] [tool.poetry.dependencies] python = "~3.11" -apscheduler = "^3.6.3" pyrss2gen = "^1.1" markdown = "^3.1.1" requests = "^2.25.1" -coverage = "^5.5" +coverage = "^6.5" peewee = "^3.14.8" -tox = "^3.24.5" background = "^0.2.1" Flask = "^2.1.1" types-markdown = "^3.4.2.1" [tool.poetry.group.dev.dependencies] -pylint = "^2.15.5" +pylint = "^2.15" mypy = "^0.991" pytest = "^7.2.0" coveralls = "^3.3.1" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index c4ac41c..0000000 --- a/tox.ini +++ /dev/null @@ -1,9 +0,0 @@ -[tox] -isolated_build = true -envlist = py38, py39 - -[testenv] -whitelist_externals = poetry -commands = - poetry install -v - poetry run pytest tests/ From 661eb35717fb98714c015147b2c6ed88eae07abd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:54:11 +0100 Subject: [PATCH 483/586] clean-up rss and mail. Add mail connection check at startup --- stacosys/run.py | 4 ++-- stacosys/service/mail.py | 23 +++++++++++++++++------ stacosys/service/rssfeed.py | 4 ++-- tests/test_rssfeed.py | 7 +++++++ 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 tests/test_rssfeed.py diff --git a/stacosys/run.py b/stacosys/run.py index 5109299..8bd26ad 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -29,7 +29,6 @@ def stacosys_server(config_pathname): logger = logging.getLogger(__name__) configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING - logging.getLogger("apscheduler.executors").level = logging.WARNING # check config file exists if not os.path.isfile(config_pathname): @@ -58,8 +57,8 @@ def stacosys_server(config_pathname): # generate RSS rss.configure( config.get(ConfigParameter.RSS_FILE), - config.get(ConfigParameter.SITE_PROTO), config.get(ConfigParameter.SITE_NAME), + config.get(ConfigParameter.SITE_PROTO), config.get(ConfigParameter.SITE_URL), ) rss.generate() @@ -72,6 +71,7 @@ def stacosys_server(config_pathname): config.get(ConfigParameter.SMTP_PASSWORD), ) mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) + mailer.check() logger.info("start interfaces %s %s %s", api, form, admin) diff --git a/stacosys/service/mail.py b/stacosys/service/mail.py index 6d756bb..d7fe5ca 100644 --- a/stacosys/service/mail.py +++ b/stacosys/service/mail.py @@ -32,6 +32,13 @@ class Mailer: def configure_destination(self, site_admin_email) -> None: self._site_admin_email = site_admin_email + def check(self): + server = smtplib.SMTP_SSL( + self._smtp_host, self._smtp_port, context=ssl.create_default_context() + ) + server.login(self._smtp_login, self._smtp_password) + server.close() + def send(self, subject, message) -> bool: sender = self._smtp_login receivers = [self._site_admin_email] @@ -41,11 +48,15 @@ class Mailer: msg["To"] = self._site_admin_email msg["From"] = sender - context = ssl.create_default_context() - # TODO catch SMTP failure - with smtplib.SMTP_SSL( - self._smtp_host, self._smtp_port, context=context - ) as server: + # pylint: disable=bare-except + try: + server = smtplib.SMTP_SSL( + self._smtp_host, self._smtp_port, context=ssl.create_default_context() + ) server.login(self._smtp_login, self._smtp_password) server.send_message(msg, sender, receivers) - return True + server.close() + success = True + except: + success = False + return success diff --git a/stacosys/service/rssfeed.py b/stacosys/service/rssfeed.py index a5f5c12..3e81389 100644 --- a/stacosys/service/rssfeed.py +++ b/stacosys/service/rssfeed.py @@ -19,13 +19,13 @@ class Rss: def configure( self, rss_file, - site_proto, site_name, + site_proto, site_url, ) -> None: self._rss_file = rss_file - self._site_proto = site_proto self._site_name = site_name + self._site_proto = site_proto self._site_url = site_url def generate(self) -> None: diff --git a/tests/test_rssfeed.py b/tests/test_rssfeed.py new file mode 100644 index 0000000..713eead --- /dev/null +++ b/tests/test_rssfeed.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from stacosys.service import rss + +def test_configure(): + rss.configure("comments.xml", "blog", "http", "blog.mydomain.com") From d8794bb35ee8498c0dc72c8004baa761b41370b5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:54:43 +0100 Subject: [PATCH 484/586] TODO relaunch pending comments on count request as CRON alternative --- stacosys/interface/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 8c88a2e..06aa07f 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -38,5 +38,6 @@ def query_comments(): @app.route("/api/comments/count", methods=["GET"]) def get_comments_count(): + # TODO process pending comments url = request.args.get("url", "") return jsonify({"count": dao.count_published_comments(url)}) From 9912ead51625e5c12847918d6ddbadf6bd14f2fb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:55:33 +0100 Subject: [PATCH 485/586] Add tests --- tests/test_config.py | 2 +- tests/test_db.py | 2 -- tests/test_mail.py | 11 +++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tests/test_mail.py diff --git a/tests/test_config.py b/tests/test_config.py index d0d8e75..db884c8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -24,7 +24,7 @@ def test_get(init_config): assert config.get(ConfigParameter.HTTP_HOST) == "" assert config.get(ConfigParameter.HTTP_PORT) == str(EXPECTED_HTTP_PORT) assert config.get_int(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT - with pytest.raises(AssertionError): + with pytest.raises(AssertionError): config.get_bool(ConfigParameter.DB_SQLITE_FILE) def test_put(init_config): diff --git a/tests/test_db.py b/tests/test_db.py index 1f390c3..649c5e7 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -11,7 +11,6 @@ def setup_db(): database.setup(":memory:") - def test_dao_published(setup_db): # test count published @@ -52,4 +51,3 @@ def test_dao_notified(setup_db): dao.notify_comment(c3) assert 0 == len(dao.find_not_notified_comments()) - diff --git a/tests/test_mail.py b/tests/test_mail.py new file mode 100644 index 0000000..192c3cc --- /dev/null +++ b/tests/test_mail.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import pytest +from stacosys.service import mailer + +def test_configure_and_check(): + mailer.configure_smtp("localhost", 2525, "admin", "admin") + mailer.configure_destination("admin@mydomain.com") + with pytest.raises(ConnectionRefusedError): + mailer.check() \ No newline at end of file From 1df8d3f8a4c2a79e6da9302bc2ceba121675abdf Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:56:13 +0100 Subject: [PATCH 486/586] remove dead code --- stacosys/interface/form.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 52b5bb8..20722f9 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -78,14 +78,7 @@ def submit_new_comment(comment): subject = f"STACOSYS {site_name}" if mailer.send(subject, email_body): logger.debug("new comment processed") - # save notification datetime dao.notify_comment(comment) else: logger.warning("rescheduled. send mail failure %s", subject) - - -@background.callback -def submit_new_comment_callback(future): - # TODO use future to log submit status - logger.debug(future) From 9e2e2b1750529d5c617f9a8814e7a4836c2ff697 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:56:35 +0100 Subject: [PATCH 487/586] Update workspace config --- stacosys.sublime-workspace | 3542 ++---------------------------------- 1 file changed, 114 insertions(+), 3428 deletions(-) diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index 82cfd31..a2e5ee9 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -5,7 +5,23 @@ [ [ "c", - "configuration" + "check" + ], + [ + "con", + "configure_destination" + ], + [ + "EXP", + "EXPECTED_HTTP_PORT" + ], + [ + "auth", + "auth_login" + ], + [ + "l", + "login" ], [ "assertI", @@ -36,3089 +52,79 @@ "buffers": [ { - "file": "stacosys/interface/web/admin.py", + "file": "pyproject.toml", + "redo_stack": + [ + [ + 84, + 1, + "insert", + { + "characters": ".1" + }, + "AgAAADABAAAAAAAAMAEAAAAAAAABAAAAMS8BAAAAAAAALwEAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAMQEAAAAAAAAxAQAAAAAAAAAAAAAAAPC/" + ] + ], "settings": { - "buffer_size": 2472, + "buffer_size": 620, "encoding": "UTF-8", "line_ending": "Unix" }, "undo_stack": [ - [ - 4, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADEAQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAANIAAAAAAAAA0wAAAAAAAAAAAAAAwQEAAAAAAADBAQAAAAAAAAQAAAAgICAg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0gAAAAAAAADSAAAAAAAAAAAAAAAAIINA" - ], - [ - 9, - 1, - "paste", - null, - "AQAAANMAAAAAAAAACwEAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wAAAAAAAADTAAAAAAAAAAAAAAAAAPC/" - ], - [ - 12, - 1, - "reindent", - null, - "AQAAAPkBAAAAAAAA/QEAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA+QEAAAAAAAD5AQAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": "cofn" - }, - "BAAAAP0BAAAAAAAA/gEAAAAAAAAAAAAA/gEAAAAAAAD/AQAAAAAAAAAAAAD/AQAAAAAAAAACAAAAAAAAAAAAAAACAAAAAAAAAQIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA/QEAAAAAAAD9AQAAAAAAAAAAAAAAAPC/" - ], - [ - 14, - 2, - "left_delete", - null, - "AgAAAAACAAAAAAAAAAIAAAAAAAABAAAAbv8BAAAAAAAA/wEAAAAAAAABAAAAZg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQIAAAAAAAABAgAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "insert", - { - "characters": "nfug" - }, - "BAAAAP8BAAAAAAAAAAIAAAAAAAAAAAAAAAIAAAAAAAABAgAAAAAAAAAAAAABAgAAAAAAAAICAAAAAAAAAAAAAAICAAAAAAAAAwIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA/wEAAAAAAAD/AQAAAAAAAAAAAAAAAPC/" - ], [ 16, - 2, - "left_delete", + 1, + "insert", + { + "characters": "6" + }, + "AgAAACIBAAAAAAAAIwEAAAAAAAAAAAAAIwEAAAAAAAAjAQAAAAAAAAEAAAA1", + "AQAAAAAAAAABAAAAIgEAAAAAAAAjAQAAAAAAAAAAAAAAAPC/" + ], + [ + 25, + 1, + "right_delete", null, - "AgAAAAICAAAAAAAAAgIAAAAAAAABAAAAZwECAAAAAAAAAQIAAAAAAAABAAAAdQ", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAwIAAAAAAAADAgAAAAAAAAAAAAAAAPC/" - ], - [ - 17, - 1, - "insert", - { - "characters": "ig" - }, - "AgAAAAECAAAAAAAAAgIAAAAAAAAAAAAAAgIAAAAAAAADAgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQIAAAAAAAABAgAAAAAAAAAAAAAAAPC/" - ], - [ - 18, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAAMCAAAAAAAABAIAAAAAAAAAAAAABAIAAAAAAAAFAgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAwIAAAAAAAADAgAAAAAAAAAAAAAAAPC/" - ], - [ - 19, - 1, - "insert", - { - "characters": " Config" - }, - "BwAAAAUCAAAAAAAABgIAAAAAAAAAAAAABgIAAAAAAAAHAgAAAAAAAAAAAAAHAgAAAAAAAAgCAAAAAAAAAAAAAAgCAAAAAAAACQIAAAAAAAAAAAAACQIAAAAAAAAKAgAAAAAAAAAAAAAKAgAAAAAAAAsCAAAAAAAAAAAAAAsCAAAAAAAADAIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABQIAAAAAAAAFAgAAAAAAAAAAAAAAAPC/" - ], - [ - 20, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAAwCAAAAAAAADgIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAIAAAAAAAAMAgAAAAAAAAAAAAAAAPC/" - ], - [ - 30, - 1, - "insert", - { - "characters": " " - }, - "AQAAAGcCAAAAAAAAaAIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwIAAAAAAABnAgAAAAAAAAAAAAAAAPC/" - ], - [ - 32, - 1, - "insert", - { - "characters": "config." - }, - "BwAAAGcCAAAAAAAAaAIAAAAAAAAAAAAAaAIAAAAAAABpAgAAAAAAAAAAAABpAgAAAAAAAGoCAAAAAAAAAAAAAGoCAAAAAAAAawIAAAAAAAAAAAAAawIAAAAAAABsAgAAAAAAAAAAAABsAgAAAAAAAG0CAAAAAAAAAAAAAG0CAAAAAAAAbgIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwIAAAAAAABnAgAAAAAAAAAAAAAAAPC/" - ], - [ - 33, - 1, - "insert", - { - "characters": "get" - }, - "AwAAAG4CAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABwAgAAAAAAAAAAAABwAgAAAAAAAHECAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbgIAAAAAAABuAgAAAAAAAAAAAAAAAPC/" - ], - [ - 34, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAHECAAAAAAAAcwIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "insert", - { - "characters": "Confi" - }, - "BQAAAHICAAAAAAAAcwIAAAAAAAAAAAAAcwIAAAAAAAB0AgAAAAAAAAAAAAB0AgAAAAAAAHUCAAAAAAAAAAAAAHUCAAAAAAAAdgIAAAAAAAAAAAAAdgIAAAAAAAB3AgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcgIAAAAAAAByAgAAAAAAAAAAAAAAAPC/" - ], - [ - 37, - 1, - "insert_completion", - { - "completion": "ConfigParameter", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "ConfigParameter" - }, - "AgAAAHICAAAAAAAAcgIAAAAAAAAFAAAAQ29uZmlyAgAAAAAAAIECAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwIAAAAAAAB3AgAAAAAAAAAAAAAAAPC/" - ], - [ - 38, - 1, - "insert", - { - "characters": ".W" - }, - "AgAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACDAgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAPC/" - ], - [ - 39, - 1, - "insert_completion", - { - "completion": "WEB_USERNAME", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "WEB_USERNAME" - }, - "AgAAAIICAAAAAAAAggIAAAAAAAABAAAAV4ICAAAAAAAAjgIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgwIAAAAAAACDAgAAAAAAAAAAAAAAAPC/" + "AQAAAMIBAAAAAAAAwgEAAAAAAAACAAAALjU", + "AQAAAAAAAAABAAAAwgEAAAAAAADEAQAAAAAAAAAAAAAAAPC/" ], [ 43, 1, "right_delete", null, - "AQAAAI8CAAAAAAAAjwIAAAAAAAAfAAAAIGFwcC5jb25maWcuZ2V0KCJXRUJfVVNFUk5BTUUiKQ", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjwIAAAAAAACuAgAAAAAAAAAAAAAAAPC/" - ], - [ - 50, - 1, - "paste", - null, - "AQAAAKgCAAAAAAAA0AIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqAIAAAAAAACoAgAAAAAAAAAAAAAAAPC/" - ], - [ - 51, - 1, - "insert", - { - "characters": " " - }, - "AQAAANACAAAAAAAA0QIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0AIAAAAAAADQAgAAAAAAAAAAAAAAAPC/" - ], - [ - 55, - 1, - "insert", - { - "characters": "PA" - }, - "AwAAAMcCAAAAAAAAyAIAAAAAAAAAAAAAyAIAAAAAAADIAgAAAAAAAAgAAABVU0VSTkFNRcgCAAAAAAAAyQIAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzwIAAAAAAADHAgAAAAAAAAAAAAAAAPC/" - ], - [ - 56, - 1, - "insert_completion", - { - "completion": "WEB_PASSWORD", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "WEB_PASSWORD" - }, - "AgAAAMMCAAAAAAAAwwIAAAAAAAAGAAAAV0VCX1BBwwIAAAAAAADPAgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAyQIAAAAAAADJAgAAAAAAAAAAAAAAAPC/" - ], - [ - 59, - 2, - "right_delete", - null, - "AgAAANECAAAAAAAA0QIAAAAAAAAeAAAAYXBwLmNvbmZpZy5nZXQoIldFQl9QQVNTV09SRCIp0QIAAAAAAADRAgAAAAAAAAEAAAAg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QIAAAAAAADvAgAAAAAAAAAAAAAAAPC/" - ], - [ - 69, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAKcFAAAAAAAAqAUAAAAAAAAAAAAAqAUAAAAAAACsBQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApwUAAAAAAACnBQAAAAAAAAAAAAAAAPC/" - ], - [ - 77, - 1, - "paste", - null, - "AQAAAKwFAAAAAAAAvQUAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArAUAAAAAAACsBQAAAAAAAAAAAAAAAPC/" - ], - [ - 85, - 1, - "paste", - null, - "AgAAAPMFAAAAAAAAGwYAAAAAAAAAAAAAGwYAAAAAAAAbBgAAAAAAAB4AAABhcHAuY29uZmlnLmdldCgiV0VCX1VTRVJOQU1FIik", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wUAAAAAAAARBgAAAAAAAAAAAAAAAPC/" - ], - [ - 98, - 1, - "paste", - null, - "AQAAAOwGAAAAAAAABwcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA7AYAAAAAAADsBgAAAAAAAAAAAAAAAPC/" - ], - [ - 99, - 1, - "insert", - { - "characters": " " - }, - "AQAAAAcHAAAAAAAACAcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABwcAAAAAAAAHBwAAAAAAAAAAAAAAAPC/" - ], - [ - 101, - 1, - "left_delete", - null, - "AQAAAAYHAAAAAAAABgcAAAAAAAABAAAALg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABwcAAAAAAAAHBwAAAAAAAAAAAAAAAPC/" - ], - [ - 102, - 1, - "insert", - { - "characters": ".LA" - }, - "AwAAAAYHAAAAAAAABwcAAAAAAAAAAAAABwcAAAAAAAAIBwAAAAAAAAAAAAAIBwAAAAAAAAkHAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABgcAAAAAAAAGBwAAAAAAAAAAAAAAAPC/" - ], - [ - 103, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"label\":\"LANG\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"LANG\",\"position\":{\"character\":48,\"line\":63},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.LANG\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "LANG" - }, - "AgAAAAcHAAAAAAAABwcAAAAAAAACAAAATEEHBwAAAAAAAAsHAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACQcAAAAAAAAJBwAAAAAAAAAAAAAAAPC/" - ], - [ - 104, - 1, - "insert", - { - "characters": ")" - }, - "AQAAAAsHAAAAAAAADAcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwcAAAAAAAALBwAAAAAAAAAAAAAAAPC/" - ], - [ - 107, - 1, - "right_delete", - null, - "AQAAAAwHAAAAAAAADAcAAAAAAAAdAAAAIGFwcC5jb25maWcuZ2V0KCJMQU5HIiwgImZyIik", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAcAAAAAAAApBwAAAAAAAAAAAAAAAPC/" - ], - [ - 112, - 1, - "insert", - { - "characters": "f" - }, - "AQAAAOEGAAAAAAAA4gYAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QYAAAAAAADhBgAAAAAAAAAAAAAAAPC/" - ], - [ - 116, - 1, - "right_delete", - null, - "AQAAAOEGAAAAAAAA4QYAAAAAAAABAAAAZg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QYAAAAAAADhBgAAAAAAAAAAAAAAAPC/" - ], - [ - 121, - 1, - "insert", - { - "characters": " " - }, - "AQAAAEMHAAAAAAAARAcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwcAAAAAAABDBwAAAAAAAAAAAAAAAPC/" - ], - [ - 123, - 1, - "paste", - null, - "AQAAAEMHAAAAAAAAXgcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwcAAAAAAABDBwAAAAAAAAAAAAAAAPC/" - ], - [ - 124, - 1, - "insert", - { - "characters": "S" - }, - "AQAAAF4HAAAAAAAAXwcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXgcAAAAAAABeBwAAAAAAAAAAAAAAAPC/" - ], - [ - 128, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"label\":\"SITE_URL\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"SITE_URL\",\"position\":{\"character\":44,\"line\":65},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.SITE_URL\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "SITE_URL" - }, - "AgAAAF4HAAAAAAAAXgcAAAAAAAABAAAAU14HAAAAAAAAZgcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXwcAAAAAAABfBwAAAAAAAAAAAAAAAPC/" - ], - [ - 129, - 1, - "insert", - { - "characters": ")" - }, - "AQAAAGYHAAAAAAAAZwcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZgcAAAAAAABmBwAAAAAAAAAAAAAAAPC/" - ], - [ - 132, - 1, - "right_delete", - null, - "AQAAAGcHAAAAAAAAZwcAAAAAAAAbAAAAIGFwcC5jb25maWcuZ2V0KCJTSVRFX1VSTCIp", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwcAAAAAAACCBwAAAAAAAAAAAAAAAPC/" - ], - [ - 140, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAKUIAAAAAAAApggAAAAAAAAAAAAApggAAAAAAACuCAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQgAAAAAAAClCAAAAAAAAP///////+9/" - ], - [ - 145, - 1, - "paste", - null, - "AQAAAAwBAAAAAAAALQEAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 146, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAC0BAAAAAAAALgEAAAAAAAAAAAAAyAgAAAAAAADICAAAAAAAAAgAAAAgICAgICAgIA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALQEAAAAAAAAtAQAAAAAAAAAAAAAAAPC/" - ], - [ - 150, - 1, - "insert", - { - "characters": "\nrss" - }, - "BQAAANEHAAAAAAAA0gcAAAAAAAAAAAAA0gcAAAAAAADWBwAAAAAAAAAAAADWBwAAAAAAANcHAAAAAAAAAAAAANcHAAAAAAAA2AcAAAAAAAAAAAAA2AcAAAAAAADZBwAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QcAAAAAAADRBwAAAAAAAAAAAAAAAPC/" - ], - [ - 151, - 1, - "insert", - { - "characters": " =" - }, - "AgAAANkHAAAAAAAA2gcAAAAAAAAAAAAA2gcAAAAAAADbBwAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2QcAAAAAAADZBwAAAAAAAAAAAAAAAPC/" - ], - [ - 152, - 1, - "insert", - { - "characters": " Rss" - }, - "BAAAANsHAAAAAAAA3AcAAAAAAAAAAAAA3AcAAAAAAADdBwAAAAAAAAAAAADdBwAAAAAAAN4HAAAAAAAAAAAAAN4HAAAAAAAA3wcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2wcAAAAAAADbBwAAAAAAAAAAAAAAAPC/" - ], - [ - 153, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAN8HAAAAAAAA4QcAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA3wcAAAAAAADfBwAAAAAAAAAAAAAAAPC/" - ], - [ - 157, - 1, - "insert", - { - "characters": "rss" - }, - "BAAAAOEIAAAAAAAA4ggAAAAAAAAAAAAA4ggAAAAAAADiCAAAAAAAABUAAABhcHAuY29uZmlnLmdldCgiUlNTIiniCAAAAAAAAOMIAAAAAAAAAAAAAOMIAAAAAAAA5AgAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4QgAAAAAAAD2CAAAAAAAAAAAAAAAAPC/" - ], - [ - 160, - 1, - "left_delete", - null, - "AQAAANcIAAAAAAAA1wgAAAAAAAABAAAACg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA2AgAAAAAAADYCAAAAAAAAAAAAAAAAPC/" - ], - [ - 176, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAEMDAAAAAAAARAMAAAAAAAAAAAAARAMAAAAAAABIAwAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABDAwAAAAAAAAAAAAAAAPC/" - ], - [ - 177, - 1, - "paste", - null, - "AQAAAEgDAAAAAAAAWQMAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASAMAAAAAAABIAwAAAAAAAAAAAAAAAPC/" - ], - [ - 182, - 1, - "insert", - { - "characters": "conig" - }, - "BgAAAAEFAAAAAAAAAgUAAAAAAAAAAAAAAgUAAAAAAAACBQAAAAAAABwAAABhcHAuY29uZmlnLmdldCgiTEFORyIsICJmciIpAgUAAAAAAAADBQAAAAAAAAAAAAADBQAAAAAAAAQFAAAAAAAAAAAAAAQFAAAAAAAABQUAAAAAAAAAAAAABQUAAAAAAAAGBQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAQUAAAAAAAAdBQAAAAAAAAAAAAAAAPC/" - ], - [ - 183, - 2, - "left_delete", - null, - "AgAAAAUFAAAAAAAABQUAAAAAAAABAAAAZwQFAAAAAAAABAUAAAAAAAABAAAAaQ", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABgUAAAAAAAAGBQAAAAAAAAAAAAAAAPC/" - ], - [ - 184, - 1, - "insert", - { - "characters": "fig.get" - }, - "BwAAAAQFAAAAAAAABQUAAAAAAAAAAAAABQUAAAAAAAAGBQAAAAAAAAAAAAAGBQAAAAAAAAcFAAAAAAAAAAAAAAcFAAAAAAAACAUAAAAAAAAAAAAACAUAAAAAAAAJBQAAAAAAAAAAAAAJBQAAAAAAAAoFAAAAAAAAAAAAAAoFAAAAAAAACwUAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAABAUAAAAAAAAEBQAAAAAAAAAAAAAAAPC/" - ], - [ - 185, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAAsFAAAAAAAADQUAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwUAAAAAAAALBQAAAAAAAAAAAAAAAPC/" - ], - [ - 186, - 1, - "insert", - { - "characters": "Confi" - }, - "BQAAAAwFAAAAAAAADQUAAAAAAAAAAAAADQUAAAAAAAAOBQAAAAAAAAAAAAAOBQAAAAAAAA8FAAAAAAAAAAAAAA8FAAAAAAAAEAUAAAAAAAAAAAAAEAUAAAAAAAARBQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAUAAAAAAAAMBQAAAAAAAAAAAAAAAPC/" - ], - [ - 188, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"label\":\"ConfigParameter\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"ConfigParameter\",\"position\":{\"character\":54,\"line\":46},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"05.0003.ConfigParameter\",\"kind\":7},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "ConfigParameter" - }, - "AgAAAAwFAAAAAAAADAUAAAAAAAAFAAAAQ29uZmkMBQAAAAAAABsFAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQUAAAAAAAARBQAAAAAAAAAAAAAAAPC/" - ], - [ - 189, - 1, - "insert", - { - "characters": ".L" - }, - "AgAAABsFAAAAAAAAHAUAAAAAAAAAAAAAHAUAAAAAAAAdBQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAGwUAAAAAAAAbBQAAAAAAAAAAAAAAAPC/" - ], - [ - 190, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"label\":\"LANG\",\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"LANG\",\"position\":{\"character\":66,\"line\":46},\"filePath\":\"/home/yannic/work/stacosys/stacosys/interface/web/admin.py\"},\"sortText\":\"08.9999.LANG\",\"kind\":20},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "LANG" - }, - "AgAAABwFAAAAAAAAHAUAAAAAAAABAAAATBwFAAAAAAAAIAUAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAHQUAAAAAAAAdBQAAAAAAAAAAAAAAAPC/" - ], - [ - 194, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADhCQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRhbwpmcm9tIHN0YWNvc3lzLmludGVyZmFjZSBpbXBvcnQgYXBwCmZyb20gc3RhY29zeXMuY29uZi5jb25maWcgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCmZyb20gc3RhY29zeXMuY29yZS5yc3MgaW1wb3J0IFJzcwoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIHJlcXVlc3QubWV0aG9kID09ICJQT1NUIjoKICAgICAgICB1c2VybmFtZSA9IHJlcXVlc3QuZm9ybS5nZXQoInVzZXJuYW1lIikKICAgICAgICBwYXNzd29yZCA9IHJlcXVlc3QuZm9ybS5nZXQoInBhc3N3b3JkIikKICAgICAgICBpZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgICAgICAgICBzZXNzaW9uWyJ1c2VyIl0gPSB1c2VybmFtZQogICAgICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiSWRlbnRpZmlhbnQgb3UgbW90IGRlIHBhc3NlIGluY29ycmVjdCIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKICAgICMgR0VUCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKCJsb2dpbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiKQoKCkBhcHAucm91dGUoIi93ZWIvbG9nb3V0IiwgbWV0aG9kcz1bIkdFVCJdKQpkZWYgbG9nb3V0KCk6CiAgICBzZXNzaW9uLnBvcCgidXNlciIpCiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBhZG1pbl9ob21lcGFnZSgpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIG5vdCAoInVzZXIiIGluIHNlc3Npb24gYW5kIHNlc3Npb25bInVzZXIiXSA9PSBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5XRUJfVVNFUk5BTUUpKToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIlZvdXMgYXZleiDDqXTDqSBkw6ljb25uZWN0w6kuIikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQoKICAgIGNvbW1lbnRzID0gZGFvLmZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKAogICAgICAgICJhZG1pbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiLAogICAgICAgIGNvbW1lbnRzPWNvbW1lbnRzLAogICAgICAgIGJhc2V1cmw9Y29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgYWRtaW5fYWN0aW9uKCk6CiAgICByc3MgPSBSc3MoKQogICAgY29tbWVudCA9IGRhby5maW5kX2NvbW1lbnRfYnlfaWQocmVxdWVzdC5mb3JtLmdldCgiY29tbWVudCIpKQogICAgaWYgY29tbWVudCBpcyBOb25lOgogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgaW50cm91dmFibGUiKQogICAgZWxpZiByZXF1ZXN0LmZvcm0uZ2V0KCJhY3Rpb24iKSA9PSAiQVBQUk9WRSI6CiAgICAgICAgZGFvLnB1Ymxpc2hfY29tbWVudChjb21tZW50KQogICAgICAgIHJzcy5nZW5lcmF0ZSgpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBwdWJsacOpIikKICAgIGVsc2U6CiAgICAgICAgZGFvLmRlbGV0ZV9jb21tZW50KGNvbW1lbnQpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBzdXBwcmltw6kiKQogICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2FkbWluIikKAAAAAAAAAADxCQAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIgUAAAAAAAAiBQAAAAAAAAAAAAAAAPC/" - ], - [ - 224, - 1, - "toggle_breakpoint", - null, - "AQAAAEQDAAAAAAAAfgMAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATAMAAAAAAABMAwAAAAAAAAAAAAAAAPC/" - ], - [ - 232, - 1, - "right_delete", - null, - "AQAAAEgDAAAAAAAASAMAAAAAAAA1AAAAaW1wb3J0IHBkYjsgcGRiLnNldF90cmFjZSgpICAjIGJyZWFrcG9pbnQgNzE4YjY2MDQgLy8", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfQMAAAAAAABIAwAAAAAAAAAAAAAAAEJA" - ], - [ - 234, - 1, - "left_delete", - null, - "AQAAAEMDAAAAAAAAQwMAAAAAAAABAAAACg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAARAMAAAAAAABEAwAAAAAAAAAAAAAAAAAA" - ], - [ - 237, - 1, - "right_delete", - null, - "AQAAAEMDAAAAAAAAQwMAAAAAAAAEAAAAICAgIA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABHAwAAAAAAAP///////+9/" - ], - [ - 256, - 1, - "toggle_breakpoint", - null, - "AQAAAEQDAAAAAAAAfgMAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwMAAAAAAABLAwAAAAAAAAAAAAAAAPC/" - ], - [ - 264, - 1, - "toggle_breakpoint", - null, - "AQAAAEQDAAAAAAAARAMAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGJlMjhiNmVmIC8vCg", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfQMAAAAAAAB9AwAAAAAAAAAAAAAAAPC/" - ], - [ - 267, - 2, - "toggle_breakpoint", - null, - "AgAAADcDAAAAAAAAbQMAAAAAAAAAAAAANwMAAAAAAAA3AwAAAAAAADYAAABpbXBvcnQgcGRiOyBwZGIuc2V0X3RyYWNlKCkgICMgYnJlYWtwb2ludCAzZDI2NjIzOCAvLwo", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQwMAAAAAAABDAwAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKYAAAAAAAAApgAAAAAAAAAAAAAAAADwvw" - ], - [ - 14, - 1, - "right_delete", - null, - "AQAAAK0AAAAAAAAArQAAAAAAAAABAAAALg", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAK0AAAAAAAAArQAAAAAAAAAAAAAAAADwvw" - ], - [ - 15, - 1, - "insert", - { - "characters": " " - }, - "AQAAAK0AAAAAAAAArgAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAK0AAAAAAAAArQAAAAAAAAAAAAAAAADwvw" - ], - [ - 17, - 1, - "insert", - { - "characters": "\n//" - }, - "AwAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAC2AAAAAAAAALcAAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" - ], - [ - 19, - 1, - "insert", - { - "characters": "#" - }, - "AgAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC2AAAAAAAAAAIAAAAvLw", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtQAAAAAAAAAAAAAAAADwvw" - ], - [ - 28, - 1, - "insert", - { - "characters": "config." - }, - "BwAAADECAAAAAAAAMgIAAAAAAAAAAAAAMgIAAAAAAAAzAgAAAAAAAAAAAAAzAgAAAAAAADQCAAAAAAAAAAAAADQCAAAAAAAANQIAAAAAAAAAAAAANQIAAAAAAAA2AgAAAAAAAAAAAAA2AgAAAAAAADcCAAAAAAAAAAAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADECAAAAAAAAMQIAAAAAAAAAAAAAAADwvw" - ], - [ - 39, - 1, - "insert", - { - "characters": " " - }, - "AgAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" - ], - [ - 40, - 2, - "left_delete", - null, - "AgAAALUAAAAAAAAAtQAAAAAAAAABAAAAILQAAAAAAAAAtAAAAAAAAAABAAAAIA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAtgAAAAAAAAAAAAAAAADwvw" - ], - [ - 41, - 1, - "insert", - { - "characters": "as" - }, - "AgAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" - ], - [ - 42, - 2, - "left_delete", - null, - "AgAAALUAAAAAAAAAtQAAAAAAAAABAAAAc7QAAAAAAAAAtAAAAAAAAAABAAAAYQ", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAtgAAAAAAAAAAAAAAAADwvw" - ], - [ - 43, - 1, - "insert", - { - "characters": " as" - }, - "AwAAALQAAAAAAAAAtQAAAAAAAAAAAAAAtQAAAAAAAAC2AAAAAAAAAAAAAAC2AAAAAAAAALcAAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" + "AQAAAMIAAAAAAAAAwgAAAAAAAAAWAAAAYXBzY2hlZHVsZXIgPSAiXjMuNi4zIg", + "AQAAAAAAAAABAAAA2AAAAAAAAADCAAAAAAAAAAAAAAAAAAAA" ], [ 44, 1, - "insert", - { - "characters": " cfg" - }, - "BAAAALcAAAAAAAAAuAAAAAAAAAAAAAAAuAAAAAAAAAC5AAAAAAAAAAAAAAC5AAAAAAAAALoAAAAAAAAAAAAAALoAAAAAAAAAuwAAAAAAAAAAAAAA", - "BgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtwAAAAAAAAAAAAAAAADwvw" - ], - [ - 10, - 1, - "insert", - { - "characters": "Config" - }, - "BwAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACvAAAAAAAAAA0AAABjb25maWcgYXMgY2ZnrwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAAsgAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuAAAAAAAAALsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 17, - 2, - "right_delete", - null, - "AgAAADECAAAAAAAAMQIAAAAAAAAGAAAAY29uZmlnMQIAAAAAAAAxAgAAAAAAAAEAAAAu", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAxAgAAAAAAADcCAAAAAAAAAAAAAAAA8L8" - ], - [ - 21, - 1, - "insert", - { - "characters": "," - }, - "AQAAALQAAAAAAAAAtQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 22, - 1, - "insert", - { - "characters": " ConfigPar" - }, - "CgAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC3AAAAAAAAAAAAAAC3AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAAC9AAAAAAAAAAAAAAC9AAAAAAAAAL4AAAAAAAAAAAAAAL4AAAAAAAAAvwAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC1AAAAAAAAALUAAAAAAAAAAAAAAAAA8L8" - ], - [ - 23, - 1, - "insert_completion", - { - "completion": "ConfigParameter", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "ConfigParameter" - }, - "AgAAALYAAAAAAAAAtgAAAAAAAAAJAAAAQ29uZmlnUGFytgAAAAAAAADFAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC/AAAAAAAAAL8AAAAAAAAAAAAAAAAA8L8" - ], - [ - 30, - 1, - "insert", - { - "characters": ".config" - }, - "BwAAAKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmAAAAAAAAAKYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 35, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAAAYCgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuY29uZi5jb25maWcgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCiMgaW1wb3J0IENvbmZpZywgQ29uZmlnUGFyYW1ldGVyCmZyb20gc3RhY29zeXMuY29yZS5yc3MgaW1wb3J0IFJzcwpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIHJlcXVlc3QubWV0aG9kID09ICJQT1NUIjoKICAgICAgICB1c2VybmFtZSA9IHJlcXVlc3QuZm9ybS5nZXQoInVzZXJuYW1lIikKICAgICAgICBwYXNzd29yZCA9IHJlcXVlc3QuZm9ybS5nZXQoInBhc3N3b3JkIikKICAgICAgICBpZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOgogICAgICAgICAgICBzZXNzaW9uWyJ1c2VyIl0gPSB1c2VybmFtZQogICAgICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiSWRlbnRpZmlhbnQgb3UgbW90IGRlIHBhc3NlIGluY29ycmVjdCIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKICAgICMgR0VUCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKCJsb2dpbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiKQoKCkBhcHAucm91dGUoIi93ZWIvbG9nb3V0IiwgbWV0aG9kcz1bIkdFVCJdKQpkZWYgbG9nb3V0KCk6CiAgICBzZXNzaW9uLnBvcCgidXNlciIpCiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBhZG1pbl9ob21lcGFnZSgpOgogICAgY29uZmlnID0gQ29uZmlnKCkKICAgIGlmIG5vdCAoCiAgICAgICAgInVzZXIiIGluIHNlc3Npb24KICAgICAgICBhbmQgc2Vzc2lvblsidXNlciJdID09IGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkKICAgICk6CiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJWb3VzIGF2ZXogw6l0w6kgZMOpY29ubmVjdMOpLiIpCiAgICAgICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2xvZ2luIikKCiAgICBjb21tZW50cyA9IGRhby5maW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKQogICAgcmV0dXJuIHJlbmRlcl90ZW1wbGF0ZSgKICAgICAgICAiYWRtaW5fIiArIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkxBTkcpICsgIi5odG1sIiwKICAgICAgICBjb21tZW50cz1jb21tZW50cywKICAgICAgICBiYXNldXJsPWNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfVVJMKSwKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2FkbWluIiwgbWV0aG9kcz1bIlBPU1QiXSkKZGVmIGFkbWluX2FjdGlvbigpOgogICAgcnNzID0gUnNzKCkKICAgIGNvbW1lbnQgPSBkYW8uZmluZF9jb21tZW50X2J5X2lkKHJlcXVlc3QuZm9ybS5nZXQoImNvbW1lbnQiKSkKICAgIGlmIGNvbW1lbnQgaXMgTm9uZToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIkNvbW1lbnRhaXJlIGludHJvdXZhYmxlIikKICAgIGVsaWYgcmVxdWVzdC5mb3JtLmdldCgiYWN0aW9uIikgPT0gIkFQUFJPVkUiOgogICAgICAgIGRhby5wdWJsaXNoX2NvbW1lbnQoY29tbWVudCkKICAgICAgICByc3MuZ2VuZXJhdGUoKQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgcHVibGnDqSIpCiAgICBlbHNlOgogICAgICAgIGRhby5kZWxldGVfY29tbWVudChjb21tZW50KQogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgc3VwcHJpbcOpIikKICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCgAAAAAAAAAAEwoAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACnAAAAAAAAAK0AAAAAAAAAAAAAAAAA8L8" - ], - [ - 39, - 1, "left_delete", null, - "AQAAALUAAAAAAAAAtQAAAAAAAAAIAAAAQ29uZmlnLCA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC1AAAAAAAAAL0AAAAAAAAAAAAAAAAA8L8" - ], - [ - 41, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BQAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACVAAAAAAAAAAAAAACVAAAAAAAAAJYAAAAAAAAAAAAAAJYAAAAAAAAAlwAAAAAAAAAAAAAAlwAAAAAAAACYAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAACQckA" - ], - [ - 42, - 1, - "insert", - { - "characters": " stacosys" - }, - "CQAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAACaAAAAAAAAAJsAAAAAAAAAAAAAAJsAAAAAAAAAnAAAAAAAAAAAAAAAnAAAAAAAAACdAAAAAAAAAAAAAACdAAAAAAAAAJ4AAAAAAAAAAAAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAAnwAAAAAAAACgAAAAAAAAAAAAAACgAAAAAAAAAKEAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACYAAAAAAAAAJgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 43, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAChAAAAAAAAAKEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 44, - 1, - "insert", - { - "characters": " core" - }, - "BQAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAKsAAAAAAAAArAAAAAAAAAAAAAAArAAAAAAAAACtAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 49, - 1, - "insert", - { - "characters": "re" - }, - "AwAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAIAAABuZr8AAAAAAAAAwAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC+AAAAAAAAAMAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 56, - 1, - "right_delete", - null, - "AQAAAFMCAAAAAAAAUwIAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAgAAAAAAAFMCAAAAAAAAAAAAAAAAQkA" - ], - [ - 58, - 1, - "left_delete", - null, - "AQAAAE4CAAAAAAAATgIAAAAAAAABAAAACg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABPAgAAAAAAAE8CAAAAAAAAAAAAAAAAAAA" - ], - [ - 61, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAKsCAAAAAAAArAIAAAAAAAAAAAAArAIAAAAAAACtAgAAAAAAAAAAAACtAgAAAAAAAK4CAAAAAAAAAAAAAK4CAAAAAAAArwIAAAAAAAAAAAAArwIAAAAAAACwAgAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAgAAAAAAAKsCAAAAAAAAAAAAAAAAUkA" - ], - [ - 64, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAPECAAAAAAAA8gIAAAAAAAAAAAAA8gIAAAAAAADzAgAAAAAAAAAAAADzAgAAAAAAAPQCAAAAAAAAAAAAAPQCAAAAAAAA9QIAAAAAAAAAAAAA9QIAAAAAAAD2AgAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADxAgAAAAAAAPECAAAAAAAAAAAAAAAA8L8" - ], - [ - 68, - 1, - "right_delete", - null, - "AQAAAHQDAAAAAAAAdAMAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAwAAAAAAAHQDAAAAAAAAAAAAAAAAQkA" - ], - [ - 70, - 1, - "left_delete", - null, - "AQAAAG8DAAAAAAAAbwMAAAAAAAABAAAACg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwAwAAAAAAAHADAAAAAAAAAAAAAAAAAAA" + "AQAAAMEAAAAAAAAAwQAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAwgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" ], [ 74, 1, "insert", { - "characters": "o" + "characters": "7" }, - "AQAAABsFAAAAAAAAHAUAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAbBQAAAAAAABsFAAAAAAAAAAAAAAAA8L8" + "AgAAAC4BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvAQAAAAAAAAEAAAA0", + "AQAAAAAAAAABAAAALgEAAAAAAAAvAQAAAAAAAAAAAAAAAPC/" ], [ - 75, - 1, - "left_delete", - null, - "AQAAABsFAAAAAAAAGwUAAAAAAAABAAAAbw", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAcBQAAAAAAABwFAAAAAAAAAAAAAAAA8L8" - ], - [ - 76, - 1, - "insert", - { - "characters": "core." - }, - "BQAAABsFAAAAAAAAHAUAAAAAAAAAAAAAHAUAAAAAAAAdBQAAAAAAAAAAAAAdBQAAAAAAAB4FAAAAAAAAAAAAAB4FAAAAAAAAHwUAAAAAAAAAAAAAHwUAAAAAAAAgBQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAbBQAAAAAAABsFAAAAAAAAAAAAAAAA8L8" - ], - [ - 82, + 78, 1, "right_delete", null, - "AQAAAAcGAAAAAAAABwYAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAYBgAAAAAAAAcGAAAAAAAAAAAAAAAAQkA" - ], - [ - 84, - 1, - "left_delete", - null, - "AQAAAAIGAAAAAAAAAgYAAAAAAAABAAAACg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAADBgAAAAAAAAMGAAAAAAAAAAAAAAAAAAA" - ], - [ - 89, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAE0GAAAAAAAATgYAAAAAAAAAAAAATgYAAAAAAABPBgAAAAAAAAAAAABPBgAAAAAAAFAGAAAAAAAAAAAAAFAGAAAAAAAAUQYAAAAAAAAAAAAAUQYAAAAAAABSBgAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABNBgAAAAAAAE0GAAAAAAAAAAAAAAAA8L8" - ], - [ - 94, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAFAHAAAAAAAAUQcAAAAAAAAAAAAAUQcAAAAAAABSBwAAAAAAAAAAAABSBwAAAAAAAFMHAAAAAAAAAAAAAFMHAAAAAAAAVAcAAAAAAAAAAAAAVAcAAAAAAABVBwAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABQBwAAAAAAAFAHAAAAAAAAAAAAAAAA8L8" - ], - [ - 98, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAKwHAAAAAAAArQcAAAAAAAAAAAAArQcAAAAAAACuBwAAAAAAAAAAAACuBwAAAAAAAK8HAAAAAAAAAAAAAK8HAAAAAAAAsAcAAAAAAAAAAAAAsAcAAAAAAACxBwAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsBwAAAAAAAKwHAAAAAAAAAAAAAAAA8L8" - ], - [ - 109, - 1, - "right_delete", - null, - "AQAAAG8DAAAAAAAAbwMAAAAAAAAEAAAAICAgIA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAwAAAAAAAHMDAAAAAAAA////////738" - ], - [ - 113, - 1, - "left_delete", - null, - "AQAAAG4DAAAAAAAAbgMAAAAAAAABAAAAOg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAwAAAAAAAG8DAAAAAAAAAAAAAAAAW0A" - ], - [ - 114, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAG4DAAAAAAAAbwMAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAwAAAAAAAG4DAAAAAAAAAAAAAAAA8L8" - ], - [ - 124, - 1, - "paste", - null, - "AgAAAK4AAAAAAAAA5QAAAAAAAAAAAAAA5QAAAAAAAADlAAAAAAAAADAAAABmcm9tIHN0YWNvc3lzLmNvcmUuY29uZmlnIGltcG9ydCBDb25maWdQYXJhbWV0ZXI", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADeAAAAAAAAAK4AAAAAAAAAAAAAAAAAAAA" - ], - [ - 5, - 1, - "insert", - { - "characters": ".core" - }, - "BQAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAoQAAAAAAAAChAAAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACvAAAAAAAAAAQAAABjb3JlrwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAAsgAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 22, - 1, - "right_delete", - null, - "AQAAALkCAAAAAAAAuQIAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAuQIAAAAAAAC+AgAAAAAAAAAAAAAAAPC/" - ], - [ - 28, - 1, - "right_delete", - null, - "AQAAAPoCAAAAAAAA+gIAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA+gIAAAAAAAD/AgAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "right_delete", - null, - "AQAAABsFAAAAAAAAGwUAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAGwUAAAAAAAAgBQAAAAAAAAAAAAAAAPC/" - ], - [ - 40, - 1, - "right_delete", - null, - "AQAAAEgGAAAAAAAASAYAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATQYAAAAAAABIBgAAAAAAAAAAAAAAAPC/" - ], - [ - 44, - 1, - "right_delete", - null, - "AQAAAEYHAAAAAAAARgcAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwcAAAAAAABGBwAAAAAAAAAAAAAAAPC/" - ], - [ - 48, - 1, - "right_delete", - null, - "AQAAAJ0HAAAAAAAAnQcAAAAAAAAFAAAAY29yZS4", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAogcAAAAAAACdBwAAAAAAAAAAAAAAAPC/" - ], - [ - 55, - 2, - "right_delete", - null, - "AgAAAO4AAAAAAAAA7gAAAAAAAAAgAAAAIyBpbXBvcnQgQ29uZmlnLCBDb25maWdQYXJhbWV0ZXLuAAAAAAAAAO4AAAAAAAAAAQAAAAo", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADgEAAAAAAADuAAAAAAAAAAAAAAAAAAAA" - ], - [ - 5, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACjAAAAAAAAAAQAAABjb3JlowAAAAAAAACkAAAAAAAAAAAAAACkAAAAAAAAAKUAAAAAAAAAAAAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAKgAAAAAAAAAqQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKIAAAAAAAAApgAAAAAAAAAAAAAAAADwvw" - ], - [ - 9, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAMYAAAAAAAAAxwAAAAAAAAAAAAAAxwAAAAAAAADHAAAAAAAAAAQAAABjb3JlxwAAAAAAAADIAAAAAAAAAAAAAADIAAAAAAAAAMkAAAAAAAAAAAAAAMkAAAAAAAAAygAAAAAAAAAAAAAAygAAAAAAAADLAAAAAAAAAAAAAADLAAAAAAAAAMwAAAAAAAAAAAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMoAAAAAAAAAxgAAAAAAAAAAAAAAAADwvw" - ], - [ - 12, - 1, - "insert", - { - "characters": "," - }, - "AQAAALcAAAAAAAAAuAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALcAAAAAAAAAtwAAAAAAAAAAAAAAAADwvw" - ], - [ - 13, - 1, - "insert", - { - "characters": " rss" - }, - "BAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAADwvw" - ], - [ - 16, - 1, - "right_delete", - null, - "AQAAAPkAAAAAAAAA+QAAAAAAAAAhAAAAZnJvbSBzdGFjb3N5cy5jb3JlLnJzcyBpbXBvcnQgUnNz", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABoBAAAAAAAA+QAAAAAAAAAAAAAAAAAAAA" - ], - [ - 18, - 2, - "right_delete", - null, - "AgAAAPgAAAAAAAAA+AAAAAAAAAABAAAACvgAAAAAAAAA+AAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAA" - ], - [ - 23, - 1, - "right_delete", - null, - "AQAAANUHAAAAAAAA1QcAAAAAAAALAAAAcnNzID0gUnNzKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOAHAAAAAAAA1QcAAAAAAAAAAAAAAABCQA" - ], - [ - 25, - 1, - "left_delete", - null, - "AQAAANAHAAAAAAAA0AcAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANEHAAAAAAAA0QcAAAAAAAAAAAAAAAAAAA" - ], - [ - 28, - 1, - "revert", - null, - "BAAAAAAAAAAAAAAAAAAAAAAAAAC6CQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgppbXBvcnQgaGFzaGxpYgppbXBvcnQgbG9nZ2luZwoKZnJvbSBmbGFzayBpbXBvcnQgZmxhc2gsIHJlZGlyZWN0LCByZW5kZXJfdGVtcGxhdGUsIHJlcXVlc3QsIHNlc3Npb24KCmZyb20gc3RhY29zeXMuc2VydmljZSBpbXBvcnQgY29uZmlnLCByc3MKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgphcHAuYWRkX3VybF9ydWxlKCIvd2ViIiwgZW5kcG9pbnQ9ImluZGV4IikKYXBwLmFkZF91cmxfcnVsZSgiL3dlYi8iLCBlbmRwb2ludD0iaW5kZXgiKQoKCkBhcHAuZW5kcG9pbnQoImluZGV4IikKZGVmIGluZGV4KCk6CiAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvYWRtaW4iKQoKCmRlZiBpc19sb2dpbl9vayh1c2VybmFtZSwgcGFzc3dvcmQpOiAgICAKICAgIGhhc2hlZCA9IGhhc2hsaWIuc2hhMjU2KHBhc3N3b3JkLmVuY29kZSgpKS5oZXhkaWdlc3QoKS51cHBlcigpCiAgICByZXR1cm4gKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLldFQl9VU0VSTkFNRSkgPT0gdXNlcm5hbWUKICAgICAgICBhbmQgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1BBU1NXT1JEKSA9PSBoYXNoZWQKICAgICkKCgpAYXBwLnJvdXRlKCIvd2ViL2xvZ2luIiwgbWV0aG9kcz1bIlBPU1QiLCAiR0VUIl0pCmRlZiBsb2dpbigpOgogICAgaWYgcmVxdWVzdC5tZXRob2QgPT0gIlBPU1QiOgogICAgICAgIHVzZXJuYW1lID0gcmVxdWVzdC5mb3JtLmdldCgidXNlcm5hbWUiKQogICAgICAgIHBhc3N3b3JkID0gcmVxdWVzdC5mb3JtLmdldCgicGFzc3dvcmQiKQogICAgICAgIGlmIGlzX2xvZ2luX29rKHVzZXJuYW1lLCBwYXNzd29yZCk6CiAgICAgICAgICAgIHNlc3Npb25bInVzZXIiXSA9IHVzZXJuYW1lCiAgICAgICAgICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJJZGVudGlmaWFudCBvdSBtb3QgZGUgcGFzc2UgaW5jb3JyZWN0IikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQogICAgIyBHRVQKICAgIHJldHVybiByZW5kZXJfdGVtcGxhdGUoImxvZ2luXyIgKyBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5MQU5HKSArICIuaHRtbCIpCgoKQGFwcC5yb3V0ZSgiL3dlYi9sb2dvdXQiLCBtZXRob2RzPVsiR0VUIl0pCmRlZiBsb2dvdXQoKToKICAgIHNlc3Npb24ucG9wKCJ1c2VyIikKICAgIHJldHVybiByZWRpcmVjdCgiL3dlYi9hZG1pbiIpCgoKQGFwcC5yb3V0ZSgiL3dlYi9hZG1pbiIsIG1ldGhvZHM9WyJHRVQiXSkKZGVmIGFkbWluX2hvbWVwYWdlKCk6ICAgIAogICAgaWYgbm90ICgKICAgICAgICAidXNlciIgaW4gc2Vzc2lvbgogICAgICAgIGFuZCBzZXNzaW9uWyJ1c2VyIl0gPT0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuV0VCX1VTRVJOQU1FKQogICAgKToKICAgICAgICAjIFRPRE8gbG9jYWxpemF0aW9uCiAgICAgICAgZmxhc2goIlZvdXMgYXZleiDDqXTDqSBkw6ljb25uZWN0w6kuIikKICAgICAgICByZXR1cm4gcmVkaXJlY3QoIi93ZWIvbG9naW4iKQoKICAgIGNvbW1lbnRzID0gZGFvLmZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpCiAgICByZXR1cm4gcmVuZGVyX3RlbXBsYXRlKAogICAgICAgICJhZG1pbl8iICsgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuTEFORykgKyAiLmh0bWwiLAogICAgICAgIGNvbW1lbnRzPWNvbW1lbnRzLAogICAgICAgIGJhc2V1cmw9Y29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQoKCkBhcHAucm91dGUoIi93ZWIvYWRtaW4iLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgYWRtaW5fYWN0aW9uKCk6ICAgIAogICAgY29tbWVudCA9IGRhby5maW5kX2NvbW1lbnRfYnlfaWQocmVxdWVzdC5mb3JtLmdldCgiY29tbWVudCIpKQogICAgaWYgY29tbWVudCBpcyBOb25lOgogICAgICAgICMgVE9ETyBsb2NhbGl6YXRpb24KICAgICAgICBmbGFzaCgiQ29tbWVudGFpcmUgaW50cm91dmFibGUiKQogICAgZWxpZiByZXF1ZXN0LmZvcm0uZ2V0KCJhY3Rpb24iKSA9PSAiQVBQUk9WRSI6CiAgICAgICAgZGFvLnB1Ymxpc2hfY29tbWVudChjb21tZW50KQogICAgICAgIHJzcy5nZW5lcmF0ZSgpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBwdWJsacOpIikKICAgIGVsc2U6CiAgICAgICAgZGFvLmRlbGV0ZV9jb21tZW50KGNvbW1lbnQpCiAgICAgICAgIyBUT0RPIGxvY2FsaXphdGlvbgogICAgICAgIGZsYXNoKCJDb21tZW50YWlyZSBzdXBwcmltw6kiKQogICAgcmV0dXJuIHJlZGlyZWN0KCIvd2ViL2FkbWluIikKAAAAAAAAAACoCQAAAAAAAAAAAAAAAAAAAAAAAKgJAAAAAAAAAAAAAAAAAAAAAAAAqAkAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANAHAAAAAAAA0AcAAAAAAAAAAAAAAADwvw" - ] - ] - }, - { - "file": "tests/test_config.py", - "settings": - { - "buffer_size": 1382, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAEoAAAAAAAAASwAAAAAAAAAAAAAASwAAAAAAAABLAAAAAAAAAAQAAABjb25mSwAAAAAAAABMAAAAAAAAAAAAAABMAAAAAAAAAE0AAAAAAAAAAAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAASgAAAAAAAAAAAAAAAADwvw" - ], - [ - 11, - 1, - "insert", - { - "characters": "uration" - }, - "BwAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAABdAAAAAAAAAF4AAAAAAAAAAAAAAF4AAAAAAAAAXwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" - ], - [ - 15, - 2, - "right_delete", - null, - "AgAAAGcAAAAAAAAAZwAAAAAAAAAHAAAAQ29uZmlnLGcAAAAAAAAAZwAAAAAAAAABAAAAIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGcAAAAAAAAAbgAAAAAAAAAAAAAAAADwvw" - ], - [ - 17, - 1, - "insert", - { - "characters": "\nimport" - }, - "BwAAADsAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD4AAAAAAAAAAAAAAD4AAAAAAAAAPwAAAAAAAAAAAAAAPwAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEEAAAAAAAAAAAAAAEEAAAAAAAAAQgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADsAAAAAAAAAOwAAAAAAAAAAAAAAADB4QA" - ], - [ - 18, - 1, - "insert", - { - "characters": " st" - }, - "AwAAAEIAAAAAAAAAQwAAAAAAAAAAAAAAQwAAAAAAAABEAAAAAAAAAAAAAABEAAAAAAAAAEUAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEIAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" - ], - [ - 19, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"stacosys\",\"kind\":9,\"sortText\":\"02.9999.stacosys\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "stacosys" - }, - "AgAAAEMAAAAAAAAAQwAAAAAAAAACAAAAc3RDAAAAAAAAAEsAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEUAAAAAAAAARQAAAAAAAAAAAAAAAADwvw" - ], - [ - 20, - 1, - "insert", - { - "characters": ".core" - }, - "BQAAAEsAAAAAAAAATAAAAAAAAAAAAAAATAAAAAAAAABNAAAAAAAAAAAAAABNAAAAAAAAAE4AAAAAAAAAAAAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEsAAAAAAAAASwAAAAAAAAAAAAAAAADwvw" - ], - [ - 21, - 4, - "left_delete", - null, - "BAAAAE8AAAAAAAAATwAAAAAAAAABAAAAZU4AAAAAAAAATgAAAAAAAAABAAAAck0AAAAAAAAATQAAAAAAAAABAAAAb0wAAAAAAAAATAAAAAAAAAABAAAAYw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFAAAAAAAAAAUAAAAAAAAAAAAAAAAADwvw" - ], - [ - 22, - 1, - "insert", - { - "characters": "ser" - }, - "AwAAAEwAAAAAAAAATQAAAAAAAAAAAAAATQAAAAAAAABOAAAAAAAAAAAAAABOAAAAAAAAAE8AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAADwvw" - ], - [ - 23, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"service\",\"kind\":9,\"sortText\":\"02.9999.service\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "service" - }, - "AgAAAEwAAAAAAAAATAAAAAAAAAADAAAAc2VyTAAAAAAAAABTAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" - ], - [ - 24, - 1, - "insert", - { - "characters": "." - }, - "AQAAAFMAAAAAAAAAVAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFMAAAAAAAAAUwAAAAAAAAAAAAAAAADwvw" - ], - [ - 27, - 1, - "insert", - { - "characters": "from" - }, - "BQAAADwAAAAAAAAAPQAAAAAAAAAAAAAAPQAAAAAAAAA9AAAAAAAAAAYAAABpbXBvcnQ9AAAAAAAAAD4AAAAAAAAAAAAAAD4AAAAAAAAAPwAAAAAAAAAAAAAAPwAAAAAAAABAAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADwAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" - ], - [ - 28, - 1, - "insert", - { - "characters": " " - }, - "AQAAAEAAAAAAAAAAQQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAADwvw" - ], - [ - 31, - 1, - "left_delete", - null, - "AQAAAEEAAAAAAAAAQQAAAAAAAAABAAAAIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEIAAAAAAAAAQgAAAAAAAAAAAAAAAADwvw" - ], - [ - 33, - 1, - "left_delete", - null, - "AQAAAFEAAAAAAAAAUQAAAAAAAAABAAAALg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" - ], - [ - 34, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABWAAAAAAAAAAAAAABWAAAAAAAAAFcAAAAAAAAAAAAAAFcAAAAAAAAAWAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFEAAAAAAAAAUQAAAAAAAAAAAAAAAADwvw" - ], - [ - 35, - 1, - "insert", - { - "characters": " " - }, - "AQAAAFgAAAAAAAAAWQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" - ], - [ - 39, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"config\",\"data\":{\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"config\",\"position\":{\"character\":29,\"line\":5},\"filePath\":\"/home/yannic/work/stacosys/tests/test_config.py\"},\"kind\":6,\"sortText\":\"09.9999.config\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "config" - }, - "AQAAAFkAAAAAAAAAXwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" - ], - [ - 58, - 1, - "right_delete", - null, - "AQAAADkBAAAAAAAAOQEAAAAAAAAUAAAAc2VsZi5jb25mID0gQ29uZmlnKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0BAAAAAAAAOQEAAAAAAAAAAAAAAABSQA" - ], - [ - 60, - 1, - "left_delete", - null, - "AQAAADABAAAAAAAAMAEAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADEBAAAAAAAAMQEAAAAAAAAAAAAAAAAAAA" - ], - [ - 64, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAEEBAAAAAAAAQgEAAAAAAAAAAAAAQgEAAAAAAABCAQAAAAAAAAkAAABzZWxmLmNvbmZCAQAAAAAAAEMBAAAAAAAAAAAAAEMBAAAAAAAARAEAAAAAAAAAAAAARAEAAAAAAABFAQAAAAAAAAAAAABFAQAAAAAAAEYBAAAAAAAAAAAAAEYBAAAAAAAARwEAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoBAAAAAAAAQQEAAAAAAAAAAAAAAADwvw" - ], - [ - 68, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAI0BAAAAAAAAjgEAAAAAAAAAAAAAjgEAAAAAAACOAQAAAAAAAAkAAABzZWxmLmNvbmaOAQAAAAAAAI8BAAAAAAAAAAAAAI8BAAAAAAAAkAEAAAAAAAAAAAAAkAEAAAAAAACRAQAAAAAAAAAAAACRAQAAAAAAAJIBAAAAAAAAAAAAAJIBAAAAAAAAkwEAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJYBAAAAAAAAjQEAAAAAAAAAAAAAAABSQA" - ], - [ - 79, - 1, - "paste", - null, - "AgAAAPsBAAAAAAAAAQIAAAAAAAAAAAAAAQIAAAAAAAABAgAAAAAAAAkAAABzZWxmLmNvbmY", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPsBAAAAAAAABAIAAAAAAAAAAAAAAADwvw" - ], - [ - 84, - 1, - "paste", - null, - "AgAAAGkCAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABvAgAAAAAAAAkAAABzZWxmLmNvbmY", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHICAAAAAAAAaQIAAAAAAAAAAAAAAADwvw" - ], - [ - 89, - 1, - "paste", - null, - "AgAAANACAAAAAAAA1gIAAAAAAAAAAAAA1gIAAAAAAADWAgAAAAAAAAkAAABzZWxmLmNvbmY", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANACAAAAAAAA2QIAAAAAAAAAAAAAAADwvw" - ], - [ - 94, - 1, - "paste", - null, - "AgAAACEDAAAAAAAAJwMAAAAAAAAAAAAAJwMAAAAAAAAnAwAAAAAAAAkAAABzZWxmLmNvbmY", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAACoDAAAAAAAAIQMAAAAAAAAAAAAAAADwvw" - ], - [ - 99, - 1, - "paste", - null, - "AgAAAIMDAAAAAAAAiQMAAAAAAAAAAAAAiQMAAAAAAACJAwAAAAAAAAkAAABzZWxmLmNvbmY", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMDAAAAAAAAjAMAAAAAAAAAAAAAAADwvw" - ], - [ - 145, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAM0DAAAAAAAAzgMAAAAAAAAAAAAAzgMAAAAAAADOAwAAAAAAAAkAAABzZWxmLmNvbmbOAwAAAAAAAM8DAAAAAAAAAAAAAM8DAAAAAAAA0AMAAAAAAAAAAAAA0AMAAAAAAADRAwAAAAAAAAAAAADRAwAAAAAAANIDAAAAAAAAAAAAANIDAAAAAAAA0wMAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANYDAAAAAAAAzQMAAAAAAAAAAAAAAADwvw" - ], - [ - 150, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAIIEAAAAAAAAgwQAAAAAAAAAAAAAgwQAAAAAAACDBAAAAAAAAAkAAABzZWxmLmNvbmaDBAAAAAAAAIQEAAAAAAAAAAAAAIQEAAAAAAAAhQQAAAAAAAAAAAAAhQQAAAAAAACGBAAAAAAAAAAAAACGBAAAAAAAAIcEAAAAAAAAAAAAAIcEAAAAAAAAiAQAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIEAAAAAAAAiwQAAAAAAAAAAAAAAADwvw" - ], - [ - 155, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAK8EAAAAAAAAsAQAAAAAAAAAAAAAsAQAAAAAAACwBAAAAAAAAAkAAABzZWxmLmNvbmawBAAAAAAAALEEAAAAAAAAAAAAALEEAAAAAAAAsgQAAAAAAAAAAAAAsgQAAAAAAACzBAAAAAAAAAAAAACzBAAAAAAAALQEAAAAAAAAAAAAALQEAAAAAAAAtQQAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgEAAAAAAAArwQAAAAAAAAAAAAAAABSQA" - ], - [ - 160, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAPcEAAAAAAAA+AQAAAAAAAAAAAAA+AQAAAAAAAD4BAAAAAAAAAkAAABzZWxmLmNvbmb4BAAAAAAAAPkEAAAAAAAAAAAAAPkEAAAAAAAA+gQAAAAAAAAAAAAA+gQAAAAAAAD7BAAAAAAAAAAAAAD7BAAAAAAAAPwEAAAAAAAAAAAAAPwEAAAAAAAA/QQAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPcEAAAAAAAAAAUAAAAAAAAAAAAAAADwvw" - ], - [ - 164, - 1, - "insert", - { - "characters": "config" - }, - "BwAAADUFAAAAAAAANgUAAAAAAAAAAAAANgUAAAAAAAA2BQAAAAAAAAkAAABzZWxmLmNvbmY2BQAAAAAAADcFAAAAAAAAAAAAADcFAAAAAAAAOAUAAAAAAAAAAAAAOAUAAAAAAAA5BQAAAAAAAAAAAAA5BQAAAAAAADoFAAAAAAAAAAAAADoFAAAAAAAAOwUAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUFAAAAAAAAPgUAAAAAAAAAAAAAAADwvw" - ] - ] - }, - { - "file": "Dockerfile", - "settings": - { - "buffer_size": 716, - "line_ending": "Unix" - } - }, - { - "file": "tests/test_api.py", - "settings": - { - "buffer_size": 1634, - "encoding": "UTF-8", - "line_ending": "Unix" - } - }, - { - "file": "stacosys/service/__init__.py", - "settings": - { - "buffer_size": 180, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 4, - 1, - "insert", - { - "characters": "from" - }, - "BAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 5, - 1, - "insert", - { - "characters": " .maile" - }, - "BwAAAFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAFgAAAAAAAAAAAAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" - ], - [ - 6, - 1, - "insert", - { - "characters": "r" - }, - "AQAAAFwAAAAAAAAAXQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" - ], - [ - 7, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": " Mailer" - }, - "BwAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABpAAAAAAAAAAAAAABpAAAAAAAAAGoAAAAAAAAAAAAAAGoAAAAAAAAAawAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": "mailer" - }, - "BgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAAAA" - ], - [ - 12, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": " " - }, - "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 17, - 2, - "left_delete", - null, - "AgAAAFwAAAAAAAAAXAAAAAAAAAABAAAAclsAAAAAAAAAWwAAAAAAAAABAAAAZQ", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 22, - 1, - "insert", - { - "characters": "Mailer" - }, - "BgAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" - ], - [ - 23, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAIwAAAAAAAAAjgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjAAAAAAAAACMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 33, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BQAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABuAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 34, - 1, - "insert", - { - "characters": " .rss" - }, - "BQAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbgAAAAAAAABuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "insert", - { - "characters": "feed" - }, - "BAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB1AAAAAAAAAAAAAAB1AAAAAAAAAHYAAAAAAAAAAAAAAHYAAAAAAAAAdwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 36, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAHcAAAAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAB5AAAAAAAAAAAAAAB5AAAAAAAAAHoAAAAAAAAAAAAAAHoAAAAAAAAAewAAAAAAAAAAAAAAewAAAAAAAAB8AAAAAAAAAAAAAAB8AAAAAAAAAH0AAAAAAAAAAAAAAH0AAAAAAAAAfgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwAAAAAAAAB3AAAAAAAAAAAAAAAAAPC/" - ], - [ - 37, - 1, - "insert", - { - "characters": " " - }, - "AQAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfgAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" - ], - [ - 38, - 1, - "insert", - { - "characters": "Rss" - }, - "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 41, - 1, - "insert", - { - "characters": "\nrss" - }, - "BAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACpAAAAAAAAAAAAAACpAAAAAAAAAKoAAAAAAAAAAAAAAKoAAAAAAAAAqwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAGtA" - ], - [ - 42, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAKsAAAAAAAAArAAAAAAAAAAAAAAArAAAAAAAAACtAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqwAAAAAAAACrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 43, - 1, - "insert", - { - "characters": " Rss" - }, - "BAAAAK0AAAAAAAAArgAAAAAAAAAAAAAArgAAAAAAAACvAAAAAAAAAAAAAACvAAAAAAAAALAAAAAAAAAAAAAAALAAAAAAAAAAsQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArQAAAAAAAACtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 44, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAALEAAAAAAAAAswAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsQAAAAAAAACxAAAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "revert", - null, - "BAAAAAAAAAAAAAAAAAAAAAAAAACzAAAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIC5jb25maWd1cmF0aW9uIGltcG9ydCBDb25maWcKZnJvbSAubWFpbCBpbXBvcnQgTWFpbGVyCmZyb20gLnJzc2ZlZWQgaW1wb3J0IFJzcwoKY29uZmlnID0gQ29uZmlnKCkKbWFpbGVyID0gTWFpbGVyKCkKcnNzID0gUnNzKCkAAAAAAAAAALQAAAAAAAAAAAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAAAAAAAAAAAAC0AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAlQAAAAAAAAAAAAAAAADwvw" - ] - ] - }, - { - "file": "stacosys/interface/form.py", - "settings": - { - "buffer_size": 2659, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "right_delete", - null, - "AQAAAJ0AAAAAAAAAnQAAAAAAAAAHAAAAQ29uZmlnLA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAKQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 9, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BQAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7AAAAAAAAAHsAAAAAAAAAAAAAAACQckA" - ], - [ - 10, - 1, - "insert", - { - "characters": " stacosys" - }, - "CQAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 11, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACJAAAAAAAAAIkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 12, - 1, - "insert", - { - "characters": " core" - }, - "BQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACSAAAAAAAAAAAAAACSAAAAAAAAAJMAAAAAAAAAAAAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACVAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACQAAAAAAAAAJAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 19, - 1, - "insert", - { - "characters": "core" - }, - "BQAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAAClAAAAAAAAAAQAAABjb25mpQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 24, - 1, - "right_delete", - null, - "AQAAAJ8BAAAAAAAAnwEAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACwAQAAAAAAAJ8BAAAAAAAAAAAAAAAAQkA" - ], - [ - 26, - 1, - "left_delete", - null, - "AQAAAJoBAAAAAAAAmgEAAAAAAAABAAAACg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAQAAAAAAAJsBAAAAAAAAAAAAAAAAAAA" - ], - [ - 31, - 1, - "insert", - { - "characters": "core." - }, - "BQAAALgFAAAAAAAAuQUAAAAAAAAAAAAAuQUAAAAAAAC6BQAAAAAAAAAAAAC6BQAAAAAAALsFAAAAAAAAAAAAALsFAAAAAAAAvAUAAAAAAAAAAAAAvAUAAAAAAAC9BQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC4BQAAAAAAALgFAAAAAAAAAAAAAAAA8L8" - ], - [ - 36, - 1, - "right_delete", - null, - "AQAAAAsHAAAAAAAACwcAAAAAAAARAAAAY29uZmlnID0gQ29uZmlnKCk", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAcBwAAAAAAAAsHAAAAAAAAAAAAAAAAQkA" - ], - [ - 38, - 1, - "left_delete", - null, - "AQAAAAYHAAAAAAAABgcAAAAAAAABAAAACg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAHBwAAAAAAAAcHAAAAAAAAAAAAAAAAAAA" - ], - [ - 43, - 1, - "insert", - { - "characters": "core." - }, - "BQAAANMIAAAAAAAA1AgAAAAAAAAAAAAA1AgAAAAAAADVCAAAAAAAAAAAAADVCAAAAAAAANYIAAAAAAAAAAAAANYIAAAAAAAA1wgAAAAAAAAAAAAA1wgAAAAAAADYCAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTCAAAAAAAANMIAAAAAAAAAAAAAAAA8L8" - ], - [ - 49, - 1, - "insert", - { - "characters": "coer." - }, - "BQAAABoHAAAAAAAAGwcAAAAAAAAAAAAAGwcAAAAAAAAcBwAAAAAAAAAAAAAcBwAAAAAAAB0HAAAAAAAAAAAAAB0HAAAAAAAAHgcAAAAAAAAAAAAAHgcAAAAAAAAfBwAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAaBwAAAAAAABoHAAAAAAAAAAAAAAAA8L8" - ], - [ - 52, - 1, - "insert", - { - "characters": "re" - }, - "AwAAABwHAAAAAAAAHQcAAAAAAAAAAAAAHQcAAAAAAAAdBwAAAAAAAAIAAABlch0HAAAAAAAAHgcAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAeBwAAAAAAABwHAAAAAAAAAAAAAAAA8L8" - ], - [ - 57, - 1, - "paste", - null, - "AgAAAJYAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADNAAAAAAAAADEAAABmcm9tIHN0YWNvc3lzLmNvcmUuY29uZmlnIGltcG9ydCAgQ29uZmlnUGFyYW1ldGVy", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADHAAAAAAAAAJYAAAAAAAAAAAAAAAAAAAA" - ], - [ - 5, - 1, - "insert", - { - "characters": ".ser" - }, - "BAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIkAAAAAAAAAiQAAAAAAAAAAAAAAAADwvw" - ], - [ - 6, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"label\":\"service\",\"kind\":9,\"sortText\":\"02.9999.service\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "service" - }, - "AgAAAIoAAAAAAAAAigAAAAAAAAADAAAAc2VyigAAAAAAAACRAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAADwvw" - ], - [ - 10, - 1, - "insert", - { - "characters": "config" - }, - "BwAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACaAAAAAAAAAAQAAABjb3JlmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAJwAAAAAAAAAnQAAAAAAAAAAAAAAnQAAAAAAAACeAAAAAAAAAAAAAACeAAAAAAAAAJ8AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJ0AAAAAAAAAmQAAAAAAAAAAAAAAAADwvw" - ], - [ - 11, - 1, - "insert", - { - "characters": "," - }, - "AQAAAJ8AAAAAAAAAoAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJ8AAAAAAAAAnwAAAAAAAAAAAAAAAADwvw" - ], - [ - 12, - 1, - "insert", - { - "characters": " mailer" - }, - "BwAAAKAAAAAAAAAAoQAAAAAAAAAAAAAAoQAAAAAAAACiAAAAAAAAAAAAAACiAAAAAAAAAKMAAAAAAAAAAAAAAKMAAAAAAAAApAAAAAAAAAAAAAAApAAAAAAAAAClAAAAAAAAAAAAAAClAAAAAAAAAKYAAAAAAAAAAAAAAKYAAAAAAAAApwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKAAAAAAAAAAoAAAAAAAAAAAAAAAAADwvw" - ], - [ - 16, - 1, - "right_delete", - null, - "AQAAAOAAAAAAAAAA4AAAAAAAAAAnAAAAZnJvbSBzdGFjb3N5cy5jb3JlLm1haWxlciBpbXBvcnQgTWFpbGVy", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAcBAAAAAAAA4AAAAAAAAAAAAAAAAAAAAA" - ], - [ - 17, - 1, - "left_delete", - null, - "AQAAAN8AAAAAAAAA3wAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOAAAAAAAAAA4AAAAAAAAAAAAAAAAADwvw" - ], - [ - 20, - 1, - "insert", - { - "characters": "service" - }, - "CAAAALYAAAAAAAAAtwAAAAAAAAAAAAAAtwAAAAAAAAC3AAAAAAAAAAQAAABjb3JltwAAAAAAAAC4AAAAAAAAAAAAAAC4AAAAAAAAALkAAAAAAAAAAAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALYAAAAAAAAAugAAAAAAAAAAAAAAAADwvw" - ], - [ - 26, - 1, - "right_delete", - null, - "AQAAAKsFAAAAAAAAqwUAAAAAAAAFAAAAY29yZS4", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAFAAAAAAAAqwUAAAAAAAAAAAAAAADwvw" - ], - [ - 33, - 1, - "right_delete", - null, - "AQAAAAgHAAAAAAAACAcAAAAAAAAFAAAAY29yZS4", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAA0HAAAAAAAACAcAAAAAAAAAAAAAAADwvw" - ], - [ - 39, - 1, - "right_delete", - null, - "AQAAAJ8IAAAAAAAAnwgAAAAAAAARAAAAbWFpbGVyID0gTWFpbGVyKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAIAAAAAAAAnwgAAAAAAAAAAAAAAABCQA" - ], - [ - 41, - 1, - "left_delete", - null, - "AQAAAJoIAAAAAAAAmggAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJsIAAAAAAAAmwgAAAAAAAAAAAAAAAAAAA" - ], - [ - 45, - 1, - "right_delete", - null, - "AQAAAK8IAAAAAAAArwgAAAAAAAAFAAAAY29yZS4", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQIAAAAAAAArwgAAAAAAAAAAAAAAADwvw" - ], - [ - 48, - 1, - "revert", - null, - "BAAAAAAAAAAAAAAAAAAAAAAAAABvCgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCmltcG9ydCBsb2dnaW5nCgppbXBvcnQgYmFja2dyb3VuZApmcm9tIGZsYXNrIGltcG9ydCBhYm9ydCwgcmVkaXJlY3QsIHJlcXVlc3QKCmZyb20gc3RhY29zeXMuc2VydmljZSBpbXBvcnQgY29uZmlnLCBtYWlsZXIKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYW8KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCgoKQGFwcC5yb3V0ZSgiL25ld2NvbW1lbnQiLCBtZXRob2RzPVsiUE9TVCJdKQpkZWYgbmV3X2Zvcm1fY29tbWVudCgpOiAgICAKICAgIGRhdGEgPSByZXF1ZXN0LmZvcm0KICAgIGxvZ2dlci5pbmZvKCJmb3JtIGRhdGEgJXMiLCBzdHIoZGF0YSkpCgogICAgIyBob25leXBvdCBmb3Igc3BhbW1lcnMKICAgIGNhcHRjaGEgPSBkYXRhLmdldCgicmVtYXJxdWUiLCAiIikKICAgIGlmIGNhcHRjaGE6CiAgICAgICAgbG9nZ2VyLndhcm5pbmcoImRpc2NhcmQgc3BhbTogZGF0YSAlcyIsIGRhdGEpCiAgICAgICAgYWJvcnQoNDAwKQoKICAgIHVybCA9IGRhdGEuZ2V0KCJ1cmwiLCAiIikKICAgIGF1dGhvcl9uYW1lID0gZGF0YS5nZXQoImF1dGhvciIsICIiKS5zdHJpcCgpCiAgICBhdXRob3JfZ3JhdmF0YXIgPSBkYXRhLmdldCgiZW1haWwiLCAiIikuc3RyaXAoKQogICAgYXV0aG9yX3NpdGUgPSBkYXRhLmdldCgic2l0ZSIsICIiKS5sb3dlcigpLnN0cmlwKCkKICAgIGlmIGF1dGhvcl9zaXRlIGFuZCBhdXRob3Jfc2l0ZVs6NF0gIT0gImh0dHAiOgogICAgICAgIGF1dGhvcl9zaXRlID0gImh0dHA6Ly8iICsgYXV0aG9yX3NpdGUKICAgIG1lc3NhZ2UgPSBkYXRhLmdldCgibWVzc2FnZSIsICIiKQoKICAgICMgYW50aS1zcGFtIGFnYWluCiAgICBpZiBub3QgdXJsIG9yIG5vdCBhdXRob3JfbmFtZSBvciBub3QgbWVzc2FnZToKICAgICAgICBsb2dnZXIud2FybmluZygiZW1wdHkgZmllbGQ6IGRhdGEgJXMiLCBkYXRhKQogICAgICAgIGFib3J0KDQwMCkKICAgIGlmIG5vdCBjaGVja19mb3JtX2RhdGEoZGF0YS50b19kaWN0KCkpOgogICAgICAgIGxvZ2dlci53YXJuaW5nKCJhZGRpdGlvbmFsIGZpZWxkOiBkYXRhICVzIiwgZGF0YSkKICAgICAgICBhYm9ydCg0MDApCgogICAgIyBhZGQgYSByb3cgdG8gQ29tbWVudCB0YWJsZQogICAgY29tbWVudCA9IGRhby5jcmVhdGVfY29tbWVudCgKICAgICAgICB1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlCiAgICApCgogICAgIyBzZW5kIG5vdGlmaWNhdGlvbiBlLW1haWwgYXN5bmNocm9ub3VzbHkKICAgIHN1Ym1pdF9uZXdfY29tbWVudChjb21tZW50KQoKICAgIHJldHVybiByZWRpcmVjdChjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TSVRFX1JFRElSRUNUKSwgY29kZT0zMDIpCgoKZGVmIGNoZWNrX2Zvcm1fZGF0YShwb3N0ZWRfY29tbWVudCk6CiAgICBmaWVsZHMgPSBbInVybCIsICJtZXNzYWdlIiwgInNpdGUiLCAicmVtYXJxdWUiLCAiYXV0aG9yIiwgInRva2VuIiwgImVtYWlsIl0KICAgIGZpbHRlcmVkID0gZGljdChmaWx0ZXIobGFtYmRhIHg6IHhbMF0gbm90IGluIGZpZWxkcywgcG9zdGVkX2NvbW1lbnQuaXRlbXMoKSkpCiAgICByZXR1cm4gbm90IGZpbHRlcmVkCgoKQGJhY2tncm91bmQudGFzawpkZWYgc3VibWl0X25ld19jb21tZW50KGNvbW1lbnQpOiAgICAKICAgIHNpdGVfdXJsID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpCiAgICBjb21tZW50X2xpc3QgPSAoCiAgICAgICAgZiJXZWIgYWRtaW4gaW50ZXJmYWNlOiB7c2l0ZV91cmx9L3dlYi9hZG1pbiIsCiAgICAgICAgIiIsCiAgICAgICAgZiJhdXRob3I6IHtjb21tZW50LmF1dGhvcl9uYW1lfSIsCiAgICAgICAgZiJzaXRlOiB7Y29tbWVudC5hdXRob3Jfc2l0ZX0iLAogICAgICAgIGYiZGF0ZToge2NvbW1lbnQuY3JlYXRlZH0iLAogICAgICAgIGYidXJsOiB7Y29tbWVudC51cmx9IiwKICAgICAgICAiIiwKICAgICAgICBjb21tZW50LmNvbnRlbnQsCiAgICAgICAgIiIsCiAgICApCiAgICBlbWFpbF9ib2R5ID0gIlxuIi5qb2luKGNvbW1lbnRfbGlzdCkKCiAgICAjIHNlbmQgZW1haWwgdG8gbm90aWZ5IGFkbWluICAgIAogICAgc2l0ZV9uYW1lID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9OQU1FKQogICAgc3ViamVjdCA9IGYiU1RBQ09TWVMge3NpdGVfbmFtZX0iCiAgICBpZiBtYWlsZXIuc2VuZChzdWJqZWN0LCBlbWFpbF9ib2R5KToKICAgICAgICBsb2dnZXIuZGVidWcoIm5ldyBjb21tZW50IHByb2Nlc3NlZCIpCgogICAgICAgICMgc2F2ZSBub3RpZmljYXRpb24gZGF0ZXRpbWUKICAgICAgICBkYW8ubm90aWZ5X2NvbW1lbnQoY29tbWVudCkKICAgIGVsc2U6CiAgICAgICAgbG9nZ2VyLndhcm5pbmcoInJlc2NoZWR1bGVkLiBzZW5kIG1haWwgZmFpbHVyZSAlcyIsIHN1YmplY3QpCgoKQGJhY2tncm91bmQuY2FsbGJhY2sKZGVmIHN1Ym1pdF9uZXdfY29tbWVudF9jYWxsYmFjayhmdXR1cmUpOgogICAgIyBUT0RPIHVzZSBmdXR1cmUgdG8gbG9nIHN1Ym1pdCBzdGF0dXMKICAgIGxvZ2dlci5kZWJ1ZyhmdXR1cmUpCgAAAAAAAAAAYwoAAAAAAAAAAAAAAAAAAAAAAABjCgAAAAAAAAAAAAAAAAAAAAAAAGMKAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADQJAAAAAAAANAkAAAAAAAAAAAAAAABiQA" - ] - ] - }, - { - "file": "stacosys/run.py", - "settings": - { - "buffer_size": 2789, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "insert", - { - "characters": "core" - }, - "BQAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABvAAAAAAAAAAQAAABjb25mbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 11, - 1, - "cut", - null, - "AQAAAIEAAAAAAAAAgQAAAAAAAAAHAAAAQ29uZmlnLA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 12, - 1, - "right_delete", - null, - "AQAAAIEAAAAAAAAAgQAAAAAAAAABAAAAIA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 14, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABfAAAAAAAAAF8AAAAAAAAAAAAAAACQckA" - ], - [ - 19, - 1, - "paste", - null, - "AQAAAGAAAAAAAAAAcgAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 20, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 21, - 1, - "insert", - { - "characters": " config" - }, - "BwAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 36, - 1, - "insert", - { - "characters": "import" - }, - "BwAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAQAAABmcm9tYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 41, - 1, - "right_delete", - null, - "AQAAAHQAAAAAAAAAdAAAAAAAAAAOAAAAIGltcG9ydCBjb25maWc", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 53, - 1, - "right_delete", - null, - "AQAAAJ0EAAAAAAAAnQQAAAAAAAAHAAAAY29uZiA9IA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkBAAAAAAAAJ0EAAAAAAAAAAAAAAAAQkA" - ], - [ - 54, - 1, - "insert", - { - "characters": "core.c" - }, - "BgAAAJ0EAAAAAAAAngQAAAAAAAAAAAAAngQAAAAAAACfBAAAAAAAAAAAAACfBAAAAAAAAKAEAAAAAAAAAAAAAKAEAAAAAAAAoQQAAAAAAAAAAAAAoQQAAAAAAACiBAAAAAAAAAAAAACiBAAAAAAAAKMEAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdBAAAAAAAAJ0EAAAAAAAAAAAAAAAA8L8" - ], - [ - 55, - 1, - "right_delete", - null, - "AQAAAKMEAAAAAAAAowQAAAAAAAABAAAAQw", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACjBAAAAAAAAKMEAAAAAAAAAAAAAAAA8L8" - ], - [ - 79, - 1, - "insert", - { - "characters": "from" - }, - "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAA" - ], - [ - 80, - 1, - "insert", - { - "characters": " " - }, - "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 82, - 2, - "right_delete", - null, - "AgAAAGUAAAAAAAAAZQAAAAAAAAAGAAAAaW1wb3J0ZQAAAAAAAABlAAAAAAAAAAEAAAAg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABlAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 84, - 1, - "insert", - { - "characters": " imo" - }, - "BAAAAG0AAAAAAAAAbgAAAAAAAAAAAAAAbgAAAAAAAABvAAAAAAAAAAAAAABvAAAAAAAAAHAAAAAAAAAAAAAAAHAAAAAAAAAAcQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABtAAAAAAAAAG0AAAAAAAAAAAAAAAAA8L8" - ], - [ - 85, - 1, - "left_delete", - null, - "AQAAAHAAAAAAAAAAcAAAAAAAAAABAAAAbw", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABxAAAAAAAAAHEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 86, - 1, - "insert", - { - "characters": "port" - }, - "BAAAAHAAAAAAAAAAcQAAAAAAAAAAAAAAcQAAAAAAAAByAAAAAAAAAAAAAAByAAAAAAAAAHMAAAAAAAAAAAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwAAAAAAAAAHAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 87, - 1, - "right_delete", - null, - "AQAAAHQAAAAAAAAAdAAAAAAAAAABAAAALg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 88, - 1, - "insert", - { - "characters": " " - }, - "AQAAAHQAAAAAAAAAdQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 97, - 1, - "insert", - { - "characters": "core" - }, - "BAAAAOYEAAAAAAAA5wQAAAAAAAAAAAAA5wQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOkEAAAAAAAAAAAAAOkEAAAAAAAA6gQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADmBAAAAAAAAOYEAAAAAAAAAAAAAAAA8L8" - ], - [ - 100, - 1, - "insert", - { - "characters": "." - }, - "AQAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBAAAAAAAAOoEAAAAAAAAAAAAAAAA8L8" - ], - [ - 102, - 1, - "insert", - { - "characters": "ig" - }, - "AgAAAO8EAAAAAAAA8AQAAAAAAAAAAAAA8AQAAAAAAADxBAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADvBAAAAAAAAO8EAAAAAAAAAAAAAAAA8L8" - ], - [ - 107, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAHwFAAAAAAAAfQUAAAAAAAAAAAAAfQUAAAAAAAB+BQAAAAAAAAAAAAB+BQAAAAAAAH8FAAAAAAAAAAAAAH8FAAAAAAAAgAUAAAAAAAAAAAAAgAUAAAAAAACBBQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB8BQAAAAAAAHwFAAAAAAAAAAAAAAAA8L8" - ], - [ - 110, - 1, - "insert", - { - "characters": "ig" - }, - "AgAAAIUFAAAAAAAAhgUAAAAAAAAAAAAAhgUAAAAAAACHBQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFBQAAAAAAAIUFAAAAAAAAAAAAAAAA8L8" - ], - [ - 116, - 1, - "insert", - { - "characters": "coer." - }, - "BQAAAOEFAAAAAAAA4gUAAAAAAAAAAAAA4gUAAAAAAADjBQAAAAAAAAAAAADjBQAAAAAAAOQFAAAAAAAAAAAAAOQFAAAAAAAA5QUAAAAAAAAAAAAA5QUAAAAAAADmBQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADhBQAAAAAAAOEFAAAAAAAAAAAAAAAA8L8" - ], - [ - 117, - 3, - "left_delete", - null, - "AwAAAOUFAAAAAAAA5QUAAAAAAAABAAAALuQFAAAAAAAA5AUAAAAAAAABAAAAcuMFAAAAAAAA4wUAAAAAAAABAAAAZQ", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADmBQAAAAAAAOYFAAAAAAAAAAAAAAAA8L8" - ], - [ - 118, - 1, - "insert", - { - "characters": "re." - }, - "AwAAAOMFAAAAAAAA5AUAAAAAAAAAAAAA5AUAAAAAAADlBQAAAAAAAAAAAADlBQAAAAAAAOYFAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADjBQAAAAAAAOMFAAAAAAAAAAAAAAAA8L8" - ], - [ - 120, - 1, - "insert", - { - "characters": "ig" - }, - "AgAAAOoFAAAAAAAA6wUAAAAAAAAAAAAA6wUAAAAAAADsBQAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqBQAAAAAAAOoFAAAAAAAAAAAAAAAA8L8" - ], - [ - 126, - 1, - "insert", - { - "characters": "core." - }, - "BQAAAEwHAAAAAAAATQcAAAAAAAAAAAAATQcAAAAAAABOBwAAAAAAAAAAAABOBwAAAAAAAE8HAAAAAAAAAAAAAE8HAAAAAAAAUAcAAAAAAAAAAAAAUAcAAAAAAABRBwAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMBwAAAAAAAEwHAAAAAAAAAAAAAAAA8L8" - ], - [ - 127, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"symbolLabel\":\"Config\",\"workspacePath\":\"/home/yannic/work/stacosys\",\"position\":{\"character\":13,\"line\":62},\"filePath\":\"/home/yannic/work/stacosys/stacosys/run.py\"},\"label\":\"Config\",\"sortText\":\"05.0000.Config\",\"kind\":7},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "Config" - }, - "AQAAAFEHAAAAAAAAVwcAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABRBwAAAAAAAFEHAAAAAAAAAAAAAAAA8L8" - ], - [ - 129, - 1, - "right_delete", - null, - "AQAAAFcHAAAAAAAAVwcAAAAAAAAEAAAAY29uZg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABXBwAAAAAAAFsHAAAAAAAAAAAAAAAA8L8" - ], - [ - 134, - 1, - "insert", - { - "characters": "c" - }, - "AQAAAFEHAAAAAAAAUgcAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABRBwAAAAAAAFEHAAAAAAAAAAAAAAAA8L8" - ], - [ - 135, - 1, - "right_delete", - null, - "AQAAAFIHAAAAAAAAUgcAAAAAAAABAAAAQw", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABSBwAAAAAAAFIHAAAAAAAAAAAAAAAA8L8" - ], - [ - 146, - 1, - "paste", - null, - "AgAAAHsHAAAAAAAAhgcAAAAAAAAAAAAAhgcAAAAAAACGBwAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7BwAAAAAAAH8HAAAAAAAAAAAAAAAA8L8" - ], - [ - 150, - 1, - "paste", - null, - "AgAAAK4HAAAAAAAAuQcAAAAAAAAAAAAAuQcAAAAAAAC5BwAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuBwAAAAAAALIHAAAAAAAAAAAAAAAA8L8" - ], - [ - 154, - 1, - "paste", - null, - "AgAAAOIHAAAAAAAA7QcAAAAAAAAAAAAA7QcAAAAAAADtBwAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADiBwAAAAAAAOYHAAAAAAAAAAAAAAAA8L8" - ], - [ - 158, - 1, - "paste", - null, - "AgAAABYIAAAAAAAAIQgAAAAAAAAAAAAAIQgAAAAAAAAhCAAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAWCAAAAAAAABoIAAAAAAAAAAAAAAAA8L8" - ], - [ - 170, - 1, - "paste", - null, - "AgAAAKYIAAAAAAAAsQgAAAAAAAAAAAAAsQgAAAAAAACxCAAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmCAAAAAAAAKoIAAAAAAAAAAAAAAAA8L8" - ], - [ - 175, - 1, - "paste", - null, - "AgAAANoIAAAAAAAA5QgAAAAAAAAAAAAA5QgAAAAAAADlCAAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADaCAAAAAAAAN4IAAAAAAAAAAAAAAAA8L8" - ], - [ - 180, - 1, - "paste", - null, - "AgAAABIJAAAAAAAAHQkAAAAAAAAAAAAAHQkAAAAAAAAdCQAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASCQAAAAAAABYJAAAAAAAAAAAAAAAA8L8" - ], - [ - 185, - 1, - "paste", - null, - "AgAAAEcJAAAAAAAAUgkAAAAAAAAAAAAAUgkAAAAAAABSCQAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABHCQAAAAAAAEsJAAAAAAAAAAAAAAAA8L8" - ], - [ - 190, - 1, - "paste", - null, - "AgAAAH8JAAAAAAAAigkAAAAAAAAAAAAAigkAAAAAAACKCQAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/CQAAAAAAAIMJAAAAAAAAAAAAAAAA8L8" - ], - [ - 195, - 1, - "paste", - null, - "AgAAACUKAAAAAAAAMAoAAAAAAAAAAAAAMAoAAAAAAAAwCgAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAlCgAAAAAAACkKAAAAAAAAAAAAAAAA8L8" - ], - [ - 200, - 1, - "paste", - null, - "AgAAAF4KAAAAAAAAaQoAAAAAAAAAAAAAaQoAAAAAAABpCgAAAAAAAAQAAABjb25m", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABeCgAAAAAAAGIKAAAAAAAAAAAAAAAA8L8" - ], - [ - 216, - 1, - "insert", - { - "characters": "." - }, - "AQAAAG0AAAAAAAAAbgAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABtAAAAAAAAAG0AAAAAAAAAAAAAAAAA8L8" - ], - [ - 219, - 1, - "right_delete", - null, - "AQAAAG4AAAAAAAAAbgAAAAAAAAAIAAAAIGltcG9ydCA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABuAAAAAAAAAHYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 226, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 227, - 1, - "insert", - { - "characters": " confi" - }, - "BgAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 228, - 1, - "insert", - { - "characters": "g" - }, - "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" - ], - [ - 241, - 1, - "insert", - { - "characters": "import" - }, - "BwAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAQAAABmcm9tYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 251, - 1, - "insert", - { - "characters": "." - }, - "AgAAAHQAAAAAAAAAdQAAAAAAAAAAAAAAdQAAAAAAAAB1AAAAAAAAAAgAAAAgaW1wb3J0IA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 258, - 1, - "right_delete", - null, - "AQAAAKQEAAAAAAAApAQAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACpBAAAAAAAAKQEAAAAAAAAAAAAAAAA8L8" - ], - [ - 265, - 1, - "right_delete", - null, - "AQAAAOMEAAAAAAAA4wQAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADjBAAAAAAAAOgEAAAAAAAAAAAAAAAA8L8" - ], - [ - 270, - 1, - "right_delete", - null, - "AQAAAHQFAAAAAAAAdAUAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5BQAAAAAAAHQFAAAAAAAAAAAAAAAA8L8" - ], - [ - 297, - 1, - "insert", - { - "characters": "from" - }, - "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAA" - ], - [ - 298, - 1, - "insert", - { - "characters": " " - }, - "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 300, - 2, - "right_delete", - null, - "AgAAAGUAAAAAAAAAZQAAAAAAAAAGAAAAaW1wb3J0ZQAAAAAAAABlAAAAAAAAAAEAAAAg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABlAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 302, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 303, - 1, - "insert", - { - "characters": " " - }, - "AQAAAHkAAAAAAAAAegAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB5AAAAAAAAAHkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 304, - 1, - "right_delete", - null, - "AQAAAHoAAAAAAAAAegAAAAAAAAABAAAALg", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB6AAAAAAAAAHoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 311, - 1, - "insert", - { - "characters": "u" - }, - "AQAAAJoAAAAAAAAAmwAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 312, - 1, - "left_delete", - null, - "AQAAAJoAAAAAAAAAmgAAAAAAAAABAAAAdQ", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAAAAAAAAAJsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 313, - 1, - "insert", - { - "characters": "uration" - }, - "BwAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACcAAAAAAAAAAAAAACcAAAAAAAAAJ0AAAAAAAAAAAAAAJ0AAAAAAAAAngAAAAAAAAAAAAAAngAAAAAAAACfAAAAAAAAAAAAAACfAAAAAAAAAKAAAAAAAAAAAAAAAKAAAAAAAAAAoQAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 353, - 1, - "right_delete", - null, - "AQAAAOAFAAAAAAAA4AUAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADgBQAAAAAAAOUFAAAAAAAAAAAAAAAA8L8" - ], - [ - 359, - 1, - "right_delete", - null, - "AQAAAEYHAAAAAAAARgcAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLBwAAAAAAAEYHAAAAAAAAAAAAAAAA8L8" - ], - [ - 363, - 1, - "right_delete", - null, - "AQAAAHAHAAAAAAAAcAcAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABwBwAAAAAAAHUHAAAAAAAAAAAAAAAA8L8" - ], - [ - 367, - 1, - "right_delete", - null, - "AQAAAJ4HAAAAAAAAngcAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeBwAAAAAAAKMHAAAAAAAAAAAAAAAA8L8" - ], - [ - 371, - 1, - "right_delete", - null, - "AQAAAM0HAAAAAAAAzQcAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADNBwAAAAAAANIHAAAAAAAAAAAAAAAA8L8" - ], - [ - 375, - 1, - "right_delete", - null, - "AQAAAPwHAAAAAAAA/AcAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD8BwAAAAAAAAEIAAAAAAAAAAAAAAAA8L8" - ], - [ - 379, - 1, - "right_delete", - null, - "AQAAAIcIAAAAAAAAhwgAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACMCAAAAAAAAIcIAAAAAAAAAAAAAAAA8L8" - ], - [ - 383, - 1, - "right_delete", - null, - "AQAAALYIAAAAAAAAtggAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC2CAAAAAAAALsIAAAAAAAAAAAAAAAA8L8" - ], - [ - 387, - 1, - "right_delete", - null, - "AQAAAOkIAAAAAAAA6QgAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpCAAAAAAAAO4IAAAAAAAAAAAAAAAA8L8" - ], - [ - 391, - 1, - "right_delete", - null, - "AQAAABkJAAAAAAAAGQkAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAZCQAAAAAAAB4JAAAAAAAAAAAAAAAA8L8" - ], - [ - 395, - 1, - "right_delete", - null, - "AQAAAEwJAAAAAAAATAkAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMCQAAAAAAAFEJAAAAAAAAAAAAAAAA8L8" - ], - [ - 402, - 1, - "right_delete", - null, - "AQAAAO0JAAAAAAAA7QkAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADyCQAAAAAAAO0JAAAAAAAAAAAAAAAA8L8" - ], - [ - 406, - 1, - "right_delete", - null, - "AQAAACEKAAAAAAAAIQoAAAAAAAAFAAAAY29yZS4", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAhCgAAAAAAACYKAAAAAAAAAAAAAAAA8L8" - ], - [ - 5, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABvAAAAAAAAAAQAAABjb3JlbwAAAAAAAABwAAAAAAAAAAAAAABwAAAAAAAAAHEAAAAAAAAAAAAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAABzAAAAAAAAAHQAAAAAAAAAAAAAAHQAAAAAAAAAdQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAbgAAAAAAAAAAAAAAAADwvw" - ], - [ - 16, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACTAAAAAAAAAAQAAABjb3JlkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAJgAAAAAAAAAmQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJIAAAAAAAAAlgAAAAAAAAAAAAAAAADwvw" - ], - [ - 21, - 1, - "insert", - { - "characters": "service" - }, - "CAAAAM0AAAAAAAAAzgAAAAAAAAAAAAAAzgAAAAAAAADOAAAAAAAAAAQAAABjb3JlzgAAAAAAAADPAAAAAAAAAAAAAADPAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAA0QAAAAAAAAAAAAAA0QAAAAAAAADSAAAAAAAAAAAAAADSAAAAAAAAANMAAAAAAAAAAAAAANMAAAAAAAAA1AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAM0AAAAAAAAA0QAAAAAAAAAAAAAAAADwvw" - ], - [ - 25, - 2, - "left_delete", - null, - "AgAAANoAAAAAAAAA2gAAAAAAAAABAAAActkAAAAAAAAA2QAAAAAAAAABAAAAZQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANsAAAAAAAAA2wAAAAAAAAAAAAAAAADwvw" - ], - [ - 30, - 1, - "insert", - { - "characters": "," - }, - "AQAAAIMAAAAAAAAAhAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" - ], - [ - 31, - 1, - "insert", - { - "characters": " a" - }, - "AgAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIQAAAAAAAAAhAAAAAAAAAAAAAAAAADwvw" - ], - [ - 32, - 1, - "left_delete", - null, - "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAYQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIYAAAAAAAAAhgAAAAAAAAAAAAAAAADwvw" - ], - [ - 33, - 1, - "insert", - { - "characters": "mailer," - }, - "BwAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAAiQAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" - ], - [ - 34, - 1, - "insert", - { - "characters": " rss" - }, - "BAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIwAAAAAAAAAjAAAAAAAAAAAAAAAAADwvw" - ], - [ - 41, - 1, - "right_delete", - null, - "AQAAAMwAAAAAAAAAzAAAAAAAAABLAAAAZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLm1haWwgaW1wb3J0IE1haWxlcgpmcm9tIHN0YWNvc3lzLmNvcmUucnNzIGltcG9ydCBSc3MK", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMwAAAAAAAAAFwEAAAAAAAAAAAAAAAAAAA" - ], - [ - 48, - 1, - "right_delete", - null, - "AQAAAOgGAAAAAAAA6AYAAAAAAAALAAAAcnNzID0gUnNzKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPMGAAAAAAAA6AYAAAAAAAAAAAAAAABCQA" - ], - [ - 50, - 1, - "left_delete", - null, - "AQAAAOMGAAAAAAAA4wYAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAOQGAAAAAAAA5AYAAAAAAAAAAAAAAAAAAA" - ], - [ - 54, - 1, - "right_delete", - null, - "AQAAABQIAAAAAAAAFAgAAAAAAAARAAAAbWFpbGVyID0gTWFpbGVyKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAACUIAAAAAAAAFAgAAAAAAAAAAAAAAABCQA" - ], - [ - 56, - 1, - "left_delete", - null, - "AQAAAA8IAAAAAAAADwgAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABAIAAAAAAAAEAgAAAAAAAAAAAAAAAAAAA" - ], - [ - 59, - 1, - "revert", - null, - "BAAAAAAAAAAAAAAAAAAAAAAAAADtCgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBhcmdwYXJzZQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHN5cwoKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlIGltcG9ydCBjb25maWcsIG1haWxlciwgcnNzCmZyb20gc3RhY29zeXMuc2VydmljZS5jb25maWd1cmF0aW9uIGltcG9ydCBDb25maWdQYXJhbWV0ZXIKZnJvbSBzdGFjb3N5cy5kYiBpbXBvcnQgZGF0YWJhc2UKZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwaSwgYXBwLCBmb3JtCmZyb20gc3RhY29zeXMuaW50ZXJmYWNlLndlYiBpbXBvcnQgYWRtaW4KCgojIGNvbmZpZ3VyZSBsb2dnaW5nCmRlZiBjb25maWd1cmVfbG9nZ2luZyhsZXZlbCk6CiAgICByb290X2xvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKICAgIHJvb3RfbG9nZ2VyLnNldExldmVsKGxldmVsKQogICAgaGFuZGxlciA9IGxvZ2dpbmcuU3RyZWFtSGFuZGxlcigpCiAgICBoYW5kbGVyLnNldExldmVsKGxldmVsKQogICAgZm9ybWF0dGVyID0gbG9nZ2luZy5Gb3JtYXR0ZXIoIlslKGFzY3RpbWUpc10gJShuYW1lKXMgJShsZXZlbG5hbWUpcyAlKG1lc3NhZ2UpcyIpCiAgICBoYW5kbGVyLnNldEZvcm1hdHRlcihmb3JtYXR0ZXIpCiAgICByb290X2xvZ2dlci5hZGRIYW5kbGVyKGhhbmRsZXIpCgoKZGVmIHN0YWNvc3lzX3NlcnZlcihjb25maWdfcGF0aG5hbWUpOgogICAgIyBjb25maWd1cmUgbG9nZ2luZwogICAgbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pCiAgICBjb25maWd1cmVfbG9nZ2luZyhsb2dnaW5nLklORk8pCiAgICBsb2dnaW5nLmdldExvZ2dlcigid2Vya3pldWciKS5sZXZlbCA9IGxvZ2dpbmcuV0FSTklORwogICAgbG9nZ2luZy5nZXRMb2dnZXIoImFwc2NoZWR1bGVyLmV4ZWN1dG9ycyIpLmxldmVsID0gbG9nZ2luZy5XQVJOSU5HCgogICAgIyBjaGVjayBjb25maWcgZmlsZSBleGlzdHMKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShjb25maWdfcGF0aG5hbWUpOgogICAgICAgIGxvZ2dlci5lcnJvcigiQ29uZmlndXJhdGlvbiBmaWxlICclcycgbm90IGZvdW5kLiIsIGNvbmZpZ19wYXRobmFtZSkKICAgICAgICBzeXMuZXhpdCgxKQoKICAgICMgbG9hZCBhbmQgY2hlY2sgY29uZmlnCiAgICBjb25maWcubG9hZChjb25maWdfcGF0aG5hbWUpCiAgICBpc19jb25maWdfb2ssIGVycmV1cl9jb25maWcgPSBjb25maWcuY2hlY2soKQogICAgaWYgbm90IGlzX2NvbmZpZ19vazoKICAgICAgICBsb2dnZXIuZXJyb3IoIkNvbmZpZ3VyYXRpb24gaW5jb3JyZWN0ZSAnJXMnIiwgZXJyZXVyX2NvbmZpZykKICAgICAgICBzeXMuZXhpdCgxKQogICAgbG9nZ2VyLmluZm8oY29uZmlnKQoKICAgICMgY2hlY2sgZGF0YWJhc2UgZmlsZSBleGlzdHMgKHByZXZlbnRzIGZyb20gY3JlYXRpbmcgYSBmcmVzaCBkYikKICAgIGRiX3BhdGhuYW1lID0gY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuREJfU1FMSVRFX0ZJTEUpCiAgICBpZiBub3QgZGJfcGF0aG5hbWUgb3Igbm90IG9zLnBhdGguaXNmaWxlKGRiX3BhdGhuYW1lKToKICAgICAgICBsb2dnZXIuZXJyb3IoIkRhdGFiYXNlIGZpbGUgJyVzJyBub3QgZm91bmQuIiwgZGJfcGF0aG5hbWUpCiAgICAgICAgc3lzLmV4aXQoMSkKCiAgICAjIGluaXRpYWxpemUgZGF0YWJhc2UKICAgIGRhdGFiYXNlLnNldHVwKGRiX3BhdGhuYW1lKQoKICAgIGxvZ2dlci5pbmZvKCJTdGFydCBTdGFjb3N5cyBhcHBsaWNhdGlvbiIpCgogICAgIyBnZW5lcmF0ZSBSU1MgICAgCiAgICByc3MuY29uZmlndXJlKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkxBTkcpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlJTU19GSUxFKSwKICAgICAgICBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5SU1NfUFJPVE8pLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfTkFNRSksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQogICAgcnNzLmdlbmVyYXRlKCkKCiAgICAjIGNvbmZpZ3VyZSBtYWlsZXIgICAgCiAgICBtYWlsZXIuY29uZmlndXJlKAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfSE9TVCksCiAgICAgICAgY29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfUE9SVCksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU01UUF9MT0dJTiksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU01UUF9QQVNTV09SRCksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9BRE1JTl9FTUFJTCksCiAgICApCgogICAgbG9nZ2VyLmluZm8oInN0YXJ0IGludGVyZmFjZXMgJXMgJXMgJXMiLCBhcGksIGZvcm0sIGFkbWluKQoKICAgICMgc3RhcnQgRmxhc2sKICAgIGFwcC5ydW4oCiAgICAgICAgaG9zdD1jb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5IVFRQX0hPU1QpLAogICAgICAgIHBvcnQ9Y29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLkhUVFBfUE9SVCksCiAgICAgICAgZGVidWc9RmFsc2UsCiAgICAgICAgdXNlX3JlbG9hZGVyPUZhbHNlLAogICAgKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcigpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCJjb25maWciLCBoZWxwPSJjb25maWcgcGF0aCBuYW1lIikKICAgIGFyZ3MgPSBwYXJzZXIucGFyc2VfYXJncygpCiAgICBzdGFjb3N5c19zZXJ2ZXIoYXJncy5jb25maWcpCgAAAAAAAAAA5QoAAAAAAAAAAAAAAAAAAAAAAADlCgAAAAAAAAAAAAAAAAAAAAAAAOUKAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAA8IAAAAAAAADwgAAAAAAAAAAAAAAADwvw" - ] - ] - }, - { - "file": "Makefile", - "settings": - { - "buffer_size": 253, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 3, - 1, - "insert", - { - "characters": "poetry" - }, - "BgAAACgAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACsAAAAAAAAAAAAAACsAAAAAAAAALAAAAAAAAAAAAAAALAAAAAAAAAAtAAAAAAAAAAAAAAAtAAAAAAAAAC4AAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAKAAAAAAAAAAoAAAAAAAAAAAAAAAAAPC/" - ], - [ - 4, - 1, - "insert", - { - "characters": " run" - }, - "BAAAAC4AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMgAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 5, - 1, - "insert", - { - "characters": " " - }, - "AQAAADIAAAAAAAAAMwAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMgAAAAAAAAAyAAAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": "potr" - }, - "BAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAGYAAAAAAAAAZwAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAEJA" - ], - [ - 9, - 2, - "left_delete", - null, - "AgAAAGYAAAAAAAAAZgAAAAAAAAABAAAAcmUAAAAAAAAAZQAAAAAAAAABAAAAdA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZwAAAAAAAABnAAAAAAAAAAAAAAAAAPC/" - ], - [ - 10, - 1, - "insert", - { - "characters": "etry" - }, - "BAAAAGUAAAAAAAAAZgAAAAAAAAAAAAAAZgAAAAAAAABnAAAAAAAAAAAAAABnAAAAAAAAAGgAAAAAAAAAAAAAAGgAAAAAAAAAaQAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZQAAAAAAAABlAAAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": " run" - }, - "BAAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 12, - 1, - "insert", - { - "characters": " " - }, - "AQAAAG0AAAAAAAAAbgAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbQAAAAAAAABtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "insert", - { - "characters": "poetry" - }, - "BgAAAIYAAAAAAAAAhwAAAAAAAAAAAAAAhwAAAAAAAACIAAAAAAAAAAAAAACIAAAAAAAAAIkAAAAAAAAAAAAAAIkAAAAAAAAAigAAAAAAAAAAAAAAigAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" - ], - [ - 16, - 1, - "insert", - { - "characters": " run" - }, - "BAAAAIwAAAAAAAAAjQAAAAAAAAAAAAAAjQAAAAAAAACOAAAAAAAAAAAAAACOAAAAAAAAAI8AAAAAAAAAAAAAAI8AAAAAAAAAkAAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjAAAAAAAAACMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 17, - 1, - "insert", - { - "characters": " " - }, - "AQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" - ], - [ - 23, - 1, - "paste", - null, - "AQAAAKUAAAAAAAAAsAAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAAPC/" - ], - [ - 26, - 1, - "paste", - null, - "AQAAAOAAAAAAAAAA6wAAAAAAAAAAAAAA", - "BwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 1, - 1, - "revert", - null, - "BgAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAYWxsOiBibGFjayB0ZXN0IHR5cGVoaW50IGxpbnQgCgpibGFjazoKCXBvZXRyeSBydW4gaXNvcnQgLS1tdWx0aS1saW5lIDMgLS1wcm9maWxlIGJsYWNrIHN0YWNvc3lzLwoJcG9ldHJ5IHJ1biBibGFjayBzdGFjb3N5cy8KCnRlc3Q6Cglwb2V0cnkgcnVuIHB5dGVzdAoKdHlwZWhpbnQ6IAoJcG9ldHJ5IHJ1biBteXB5IC0taWdub3JlLW1pc3NpbmctaW1wb3J0cyBzdGFjb3N5cy8KCmxpbnQ6Cglwb2V0cnkgcnVuIHB5bGludCBzdGFjb3N5cy8KCgAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAAAAAAAAAAAAAAAAAAAAAAP0AAAAAAAAAAAAAAAAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAD9AAAAAAAAAAAAAAA", - "BQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAD7AAAAAAAAAPsAAAAAAAAAAAAAAAAA8L8" + "AQAAAC8BAAAAAAAALwEAAAAAAAACAAAALjU", + "AQAAAAAAAAABAAAALwEAAAAAAAAxAQAAAAAAAAAAAAAAAPC/" ] ] } @@ -3134,6 +140,22 @@ "last_filter": "", "selected_items": [ + [ + "comment", + "Toggle Comment" + ], + [ + "install", + "Package Control: Install Package" + ], + [ + "lsp", + "LSP: Toggle Diagnostics Panel" + ], + [ + "rename", + "LSP: Rename" + ], [ "brea", "Python Breakpoints: Clear All" @@ -3166,14 +188,6 @@ "insta", "Package Control: Install Package" ], - [ - "install", - "Package Control: Install Package" - ], - [ - "lsp", - "LSP: Toggle Diagnostics Panel" - ], [ "diag", "LSP: Toggle Diagnostics Panel" @@ -3194,10 +208,6 @@ "upg", "Package Control: Upgrade Package" ], - [ - "comment", - "Toggle Comment" - ], [ "form", "LSP: Format Document" @@ -3269,28 +279,39 @@ ], "file_history": [ + "/home/yannic/work/stacosys/stacosys/interface/form.py", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", + "/home/yannic/work/stacosys/config-dev.ini", + "/home/yannic/work/stacosys/stacosys/service/mail.py", + "/home/yannic/work/stacosys/tests/test_mail.py", + "/home/yannic/work/stacosys/Makefile", + "/home/yannic/work/stacosys/tests/test_db.py", + "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", + "/home/yannic/work/stacosys/stacosys/run.py", + "/home/yannic/work/stacosys/tests/test_config.py", + "/home/yannic/work/stacosys/.venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi", + "/usr/lib64/python3.11/smtplib.py", + "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", + "/home/yannic/work/stacosys/stacosys/service/configuration.py", + "/home/yannic/work/stacosys/config.ini", + "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", + "/home/yannic/work/stacosys/comments.xml", + "/home/yannic/work/stacosys/tests/test_api.py", + "/home/yannic/work/stacosys/stacosys/service/__init__.py", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/PyRSS2Gen.py", "/home/yannic/work/blog/README.md", "/home/yannic/work/blog/Dockerfile", "/usr/lib64/python3.11/logging/__init__.py", "/home/yannic/work/stacosys/stacosys/core/__init__.py", - "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", - "/home/yannic/work/stacosys/stacosys/service/mail.py", "/home/yannic/work/stacosys/stacosys/core/configuration.py", - "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", "/home/yannic/work/stacosys/.pylintrc", - "/home/yannic/work/stacosys/stacosys/interface/form.py", - "/home/yannic/work/stacosys/tests/test_config.py", "/home/yannic/work/stacosys/tests/test_form.py", - "/home/yannic/work/stacosys/tests/test_api.py", "/home/yannic/work/stacosys/stacosys/core/mailer.py", "/home/yannic/work/stacosys/stacosys/conf/config.py", "/home/yannic/work/stacosys/stacosys/core/rss.py", "/home/yannic/work/stacosys/stacosys/db/dao.py", - "/home/yannic/work/stacosys/stacosys/run.py", "/home/yannic/work/stacosys/stacosys/db/database.py", - "/home/yannic/work/stacosys/Makefile", "/home/yannic/work/stacosys/run.sh", - "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", "/home/yannic/work/stacosys/stacosys/interface/__init__.py", "/home/yannic/work/stacosys/stacosys/interface/api.py", "/home/yannic/work/stacosys/stacosys.sublime-project", @@ -3303,7 +324,7 @@ ], "find": { - "height": 28.0 + "height": 40.0 }, "find_in_files": { @@ -3318,6 +339,8 @@ "case_sensitive": false, "find_history": [ + "apscheduler", + "_lang", "config", "SITE_TOKE", "app.conf", @@ -3346,140 +369,25 @@ [ { "buffer": 0, - "file": "stacosys/interface/web/admin.py", - "semi_transient": false, - "settings": - { - "buffer_size": 2472, - "regions": - { - "bp-718b6604": - { - "flags": 16, - "icon": "Packages/Theme - Default/common/circle.png", - "regions": - [ - [ - 826, - 826 - ] - ], - "scope": "invalid" - } - }, - "selection": - [ - [ - 1207, - 1207 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 8, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/interface/web/admin.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 1, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 1, - "file": "tests/test_config.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1382, - "regions": - { - }, - "selection": - [ - [ - 1222, - 1222 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 4, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_config.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 2, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 2, - "file": "Dockerfile", + "file": "pyproject.toml", "selected": true, - "semi_transient": true, + "semi_transient": false, "settings": { - "buffer_size": 716, + "buffer_size": 620, "regions": { }, "selection": [ [ - 0, - 0 + 481, + 481 ] ], "settings": { - "lsp_uri": "file:///home/yannic/work/stacosys/Dockerfile", + "lsp_uri": "file:///home/yannic/work/stacosys/pyproject.toml", "syntax": "Packages/Text/Plain text.tmLanguage" }, "translation.x": 0.0, @@ -3489,240 +397,6 @@ "stack_index": 0, "stack_multiselect": false, "type": "text" - }, - { - "buffer": 3, - "file": "tests/test_api.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1634, - "regions": - { - }, - "selection": - [ - [ - 0, - 0 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 4, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 114.0, - "zoom_level": 1.0 - }, - "stack_index": 3, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 4, - "file": "stacosys/service/__init__.py", - "semi_transient": false, - "settings": - { - "buffer_size": 180, - "regions": - { - }, - "selection": - [ - [ - 149, - 149 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 5, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/__init__.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax" - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 4, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 5, - "file": "stacosys/interface/form.py", - "semi_transient": false, - "settings": - { - "buffer_size": 2659, - "regions": - { - }, - "selection": - [ - [ - 2356, - 2356 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 6, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/interface/form.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 1311.0, - "zoom_level": 1.0 - }, - "stack_index": 5, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 6, - "file": "stacosys/run.py", - "semi_transient": false, - "settings": - { - "buffer_size": 2789, - "regions": - { - }, - "selection": - [ - [ - 2063, - 2063 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 6, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 6, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 7, - "file": "Makefile", - "semi_transient": false, - "settings": - { - "buffer_size": 253, - "regions": - { - }, - "selection": - [ - [ - 251, - 251 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", - "syntax": "Packages/Makefile/Makefile.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": false - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 7, - "stack_multiselect": false, - "type": "text" } ] } @@ -3799,6 +473,18 @@ "last_filter": "", "selected_items": [ + [ + "socket.p", + ".venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi" + ], + [ + "mail", + "stacosys/service/mail.py" + ], + [ + "conf", + "stacosys/service/configuration.py" + ] ], "width": 0.0 }, @@ -3828,7 +514,7 @@ "show_open_files": false, "show_tabs": true, "side_bar_visible": true, - "side_bar_width": 302.0, + "side_bar_width": 299.0, "status_bar_visible": true, "template_settings": { From bafc0af92cd0f8fb10503fbcc93b1bbff0c7ced4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 30 Nov 2022 11:46:57 +0100 Subject: [PATCH 488/586] Share new comment function --- stacosys/interface/__init__.py | 34 ++++++++++++++++++++++++++++++++++ stacosys/interface/api.py | 6 ++++-- stacosys/interface/form.py | 32 ++------------------------------ 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/stacosys/interface/__init__.py b/stacosys/interface/__init__.py index 540e40e..d72483d 100644 --- a/stacosys/interface/__init__.py +++ b/stacosys/interface/__init__.py @@ -1,9 +1,43 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import logging + +import background from flask import Flask +from stacosys.db import dao +from stacosys.service import config, mailer +from stacosys.service.configuration import ConfigParameter + app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' + +logger = logging.getLogger(__name__) + + +@background.task +def submit_new_comment(comment): + site_url = config.get(ConfigParameter.SITE_URL) + comment_list = ( + f"Web admin interface: {site_url}/web/admin", + "", + f"author: {comment.author_name}", + f"site: {comment.author_site}", + f"date: {comment.created}", + f"url: {comment.url}", + "", + comment.content, + "", + ) + email_body = "\n".join(comment_list) + + # send email to notify admin + site_name = config.get(ConfigParameter.SITE_NAME) + subject = f"STACOSYS {site_name}" + if mailer.send(subject, email_body): + logger.debug("new comment processed") + # save notification datetime + dao.notify_comment(comment) diff --git a/stacosys/interface/api.py b/stacosys/interface/api.py index 06aa07f..13dbcff 100644 --- a/stacosys/interface/api.py +++ b/stacosys/interface/api.py @@ -6,7 +6,7 @@ import logging from flask import jsonify, request from stacosys.db import dao -from stacosys.interface import app +from stacosys.interface import app, submit_new_comment logger = logging.getLogger(__name__) @@ -38,6 +38,8 @@ def query_comments(): @app.route("/api/comments/count", methods=["GET"]) def get_comments_count(): - # TODO process pending comments + # send notification for pending e-mails asynchronously + for comment in dao.find_not_notified_comments(): + submit_new_comment(comment) url = request.args.get("url", "") return jsonify({"count": dao.count_published_comments(url)}) diff --git a/stacosys/interface/form.py b/stacosys/interface/form.py index 20722f9..8513438 100644 --- a/stacosys/interface/form.py +++ b/stacosys/interface/form.py @@ -2,12 +2,11 @@ # -*- coding: utf-8 -*- import logging -import background from flask import abort, redirect, request from stacosys.db import dao -from stacosys.interface import app -from stacosys.service import config, mailer +from stacosys.interface import app, submit_new_comment +from stacosys.service import config from stacosys.service.configuration import ConfigParameter logger = logging.getLogger(__name__) @@ -55,30 +54,3 @@ def check_form_data(posted_comment): fields = ["url", "message", "site", "remarque", "author", "token", "email"] filtered = dict(filter(lambda x: x[0] not in fields, posted_comment.items())) return not filtered - - -@background.task -def submit_new_comment(comment): - site_url = config.get(ConfigParameter.SITE_URL) - comment_list = ( - f"Web admin interface: {site_url}/web/admin", - "", - f"author: {comment.author_name}", - f"site: {comment.author_site}", - f"date: {comment.created}", - f"url: {comment.url}", - "", - comment.content, - "", - ) - email_body = "\n".join(comment_list) - - # send email to notify admin - site_name = config.get(ConfigParameter.SITE_NAME) - subject = f"STACOSYS {site_name}" - if mailer.send(subject, email_body): - logger.debug("new comment processed") - # save notification datetime - dao.notify_comment(comment) - else: - logger.warning("rescheduled. send mail failure %s", subject) From 1522f2826d763e1fd756559a4fe105901eba4ae9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 1 Dec 2022 20:35:48 +0100 Subject: [PATCH 489/586] Replace DB layer Peewee by PyDal --- config.ini | 2 +- poetry.lock | 24 +- pyproject.toml | 2 +- stacosys.sublime-workspace | 8928 ++++++++++++++++++++++++++++- stacosys/db/__init__.py | 30 + stacosys/db/dao.py | 59 +- stacosys/db/database.py | 26 - stacosys/model/comment.py | 26 +- stacosys/run.py | 8 +- stacosys/service/configuration.py | 2 +- stacosys/service/rssfeed.py | 9 +- tests/test_api.py | 2 +- tests/test_config.py | 10 +- tests/test_db.py | 2 +- tests/test_form.py | 2 +- 15 files changed, 8962 insertions(+), 170 deletions(-) delete mode 100644 stacosys/db/database.py diff --git a/config.ini b/config.ini index 5cd951a..f6ff236 100755 --- a/config.ini +++ b/config.ini @@ -2,7 +2,7 @@ ; Default configuration [main] lang = fr -db_sqlite_file = db.sqlite +db = sqlite://db.sqlite [site] name = "My blog" diff --git a/poetry.lock b/poetry.lock index c5a9026..2bb08e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -315,14 +315,6 @@ category = "dev" optional = false python-versions = ">=3.7" -[[package]] -name = "peewee" -version = "3.15.4" -description = "a little orm" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "platformdirs" version = "2.5.4" @@ -355,6 +347,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "pydal" +version = "20221110.1" +description = "a pure Python Database Abstraction Layer (for python version 2.7 and 3.x)" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyflakes" version = "3.0.1" @@ -524,7 +524,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "~3.11" -content-hash = "c7fd5e51d22b64ab20394250b12819609483e7e825996bb8c62e6bb2bb537951" +content-hash = "eae5d8539c8fd2e80b005124c5439a61310128f7427bdc20affa92dec85085ee" [metadata.files] astroid = [ @@ -788,9 +788,6 @@ pathspec = [ {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, ] -peewee = [ - {file = "peewee-3.15.4.tar.gz", hash = "sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"}, -] platformdirs = [ {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, @@ -803,6 +800,9 @@ pycodestyle = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] +pydal = [ + {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, +] pyflakes = [ {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, diff --git a/pyproject.toml b/pyproject.toml index f048766..3eff290 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,10 +12,10 @@ pyrss2gen = "^1.1" markdown = "^3.1.1" requests = "^2.25.1" coverage = "^6.5" -peewee = "^3.14.8" background = "^0.2.1" Flask = "^2.1.1" types-markdown = "^3.4.2.1" +pydal = "^20221110.1" [tool.poetry.group.dev.dependencies] pylint = "^2.15" diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index a2e5ee9..00ac913 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -3,6 +3,26 @@ { "selected_items": [ + [ + "au", + "author_gravatar=" + ], + [ + "auth", + "author_site=" + ], + [ + "db", + "db_uri" + ], + [ + "db_", + "db_uri" + ], + [ + "r", + "rss" + ], [ "c", "check" @@ -15,10 +35,6 @@ "EXP", "EXPECTED_HTTP_PORT" ], - [ - "auth", - "auth_login" - ], [ "l", "login" @@ -52,79 +68,8239 @@ "buffers": [ { - "file": "pyproject.toml", - "redo_stack": - [ - [ - 84, - 1, - "insert", - { - "characters": ".1" - }, - "AgAAADABAAAAAAAAMAEAAAAAAAABAAAAMS8BAAAAAAAALwEAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAMQEAAAAAAAAxAQAAAAAAAAAAAAAAAPC/" - ] - ], + "file": "stacosys/db/__init__.py", "settings": { - "buffer_size": 620, + "buffer_size": 672, "encoding": "UTF-8", "line_ending": "Unix" }, "undo_stack": [ [ - 16, + 1, + 1, + "paste", + null, + "AQAAAAAAAAAAAAAALQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 3, 1, "insert", { - "characters": "6" + "characters": "\n\nd" }, - "AgAAACIBAAAAAAAAIwEAAAAAAAAAAAAAIwEAAAAAAAAjAQAAAAAAAAEAAAA1", - "AQAAAAAAAAABAAAAIgEAAAAAAAAjAQAAAAAAAAAAAAAAAPC/" + "AwAAAC0AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAADAAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAA8L8" ], [ - 25, + 4, 1, - "right_delete", - null, - "AQAAAMIBAAAAAAAAwgEAAAAAAAACAAAALjU", - "AQAAAAAAAAABAAAAwgEAAAAAAADEAQAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": "b" + }, + "AQAAADAAAAAAAAAAMQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAwAAAAAAAAADAAAAAAAAAAAAAAAAAA8L8" ], [ - 43, + 5, 1, - "right_delete", - null, - "AQAAAMIAAAAAAAAAwgAAAAAAAAAWAAAAYXBzY2hlZHVsZXIgPSAiXjMuNi4zIg", - "AQAAAAAAAAABAAAA2AAAAAAAAADCAAAAAAAAAAAAAAAAAAAA" + "insert", + { + "characters": " =" + }, + "AgAAADEAAAAAAAAAMgAAAAAAAAAAAAAAMgAAAAAAAAAzAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAxAAAAAAAAADEAAAAAAAAAAAAAAAAA8L8" ], [ - 44, + 6, + 1, + "insert", + { + "characters": " None" + }, + "BQAAADMAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADYAAAAAAAAAAAAAADYAAAAAAAAANwAAAAAAAAAAAAAANwAAAAAAAAA4AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 11, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAC4AAAAAAAAALwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAuAAAAAAAAAC4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 12, + 1, + "paste", + null, + "AQAAAC8AAAAAAAAASwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 13, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAEsAAAAAAAAATAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 15, + 1, + "insert", + { + "characters": "\n\n" + }, + "AgAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABWAAAAAAAAAFYAAAAAAAAAAAAAAAAAAAA" + ], + [ + 16, + 1, + "paste", + null, + "AQAAAFgAAAAAAAAA6wAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABYAAAAAAAAAFgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 21, + 1, + "insert", + { + "characters": "i" + }, + "AgAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABoAAAAAAAAAAEAAABs", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABoAAAAAAAAAGcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 29, + 1, + "insert", + { + "characters": "db" + }, + "AwAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAA8AAABkYi5pbml0KGRiX3VybClwAAAAAAAAAHEAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAAAAAAAAAH4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 30, + 1, + "insert", + { + "characters": " " + }, + "AgAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABxAAAAAAAAAHEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 31, 1, "left_delete", null, - "AQAAAMEAAAAAAAAAwQAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAwgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" + "AQAAAHIAAAAAAAAAcgAAAAAAAAABAAAAIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABzAAAAAAAAAHMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 32, + 1, + "insert", + { + "characters": "=" + }, + "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 33, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABzAAAAAAAAAHMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 35, + 1, + "", + null, + "AQAAAHQAAAAAAAAAjgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 39, + 1, + "insert", + { + "characters": "db_" + }, + "BAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAB5AAAAAAAAABUAAAAnc3FsaXRlOi8vc3RvcmFnZS5kYid5AAAAAAAAAHoAAAAAAAAAAAAAAHoAAAAAAAAAewAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACNAAAAAAAAAHgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 42, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"09.9999.db_uri\",\"kind\":6,\"data\":{\"position\":{\"line\":8,\"character\":16},\"symbolLabel\":\"db_uri\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_uri\"},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "db_uri" + }, + "AgAAAHgAAAAAAAAAeAAAAAAAAAADAAAAZGJfeAAAAAAAAAB+AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7AAAAAAAAAHsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 46, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 48, + 1, + "trim_trailing_white_space", + null, + "AQAAAIAAAAAAAAAAgAAAAAAAAAAEAAAAICAgIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACEAAAAAAAAAIQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 55, + 1, + "reindent", + null, + "AQAAAIAAAAAAAAAAhAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 56, + 1, + "insert", + { + "characters": "db." + }, + "AwAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAACGAAAAAAAAAIcAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACEAAAAAAAAAIQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 57, + 1, + "insert", + { + "characters": "defi" + }, + "BAAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACHAAAAAAAAAIcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 58, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"09.9999.define_table\",\"kind\":2,\"data\":{\"position\":{\"line\":9,\"character\":11},\"symbolLabel\":\"define_table\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"define_table\"},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "define_table" + }, + "AgAAAIcAAAAAAAAAhwAAAAAAAAAEAAAAZGVmaYcAAAAAAAAAkwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACLAAAAAAAAAIsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 59, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAJMAAAAAAAAAlQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 60, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAJQAAAAAAAAAlgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACUAAAAAAAAAJQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 61, + 1, + "insert", + { + "characters": "comment" + }, + "BwAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAACaAAAAAAAAAJsAAAAAAAAAAAAAAJsAAAAAAAAAnAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACVAAAAAAAAAJUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 67, + 1, + "left_delete", + null, + "AQAAAJ8AAAAAAAAAnwAAAAAAAABsAAAAICAgIGRiLmNvbm5lY3QoKQoKICAgIGZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKICAgIGRiLmNyZWF0ZV90YWJsZXMoW0NvbW1lbnRdLCBzYWZlPVRydWUp", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAALAQAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 70, + 1, + "insert", + { + "characters": ";" + }, + "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAJ0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 71, + 1, + "left_delete", + null, + "AQAAAJ0AAAAAAAAAnQAAAAAAAAABAAAAOw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 72, + 1, + "insert", + { + "characters": "," + }, + "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAJ0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 73, + 1, + "insert", + { + "characters": " " + }, + "AQAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" ], [ 74, 1, "insert", { - "characters": "7" + "characters": "Field" }, - "AgAAAC4BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvAQAAAAAAAAEAAAA0", - "AQAAAAAAAAABAAAALgEAAAAAAAAvAQAAAAAAAAAAAAAAAPC/" + "BQAAAJ8AAAAAAAAAoAAAAAAAAAAAAAAAoAAAAAAAAAChAAAAAAAAAAAAAAChAAAAAAAAAKIAAAAAAAAAAAAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACkAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACfAAAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 75, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAKQAAAAAAAAApgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkAAAAAAAAAKQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 76, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAKUAAAAAAAAApwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 77, + 1, + "paste", + null, + "AQAAAKYAAAAAAAAAqQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmAAAAAAAAAKYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 81, + 1, + "insert", + { + "characters": "," + }, + "AQAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 82, + 1, + "insert", + { + "characters": " F" + }, + "AgAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 83, + 1, + "insert", + { + "characters": "ield" + }, + "BAAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuAAAAAAAAAK4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 84, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAALIAAAAAAAAAtAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAAAAAAAAALIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 85, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAALMAAAAAAAAAtQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAAAAAAAAALMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 86, + 1, + "paste", + null, + "AQAAALQAAAAAAAAAuwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 88, + 1, + "insert", + { + "characters": "," + }, + "AQAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC8AAAAAAAAALwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 89, + 1, + "insert", + { + "characters": " type=" + }, + "BgAAAL0AAAAAAAAAvgAAAAAAAAAAAAAAvgAAAAAAAAC/AAAAAAAAAAAAAAC/AAAAAAAAAMAAAAAAAAAAAAAAAMAAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAADCAAAAAAAAAAAAAADCAAAAAAAAAMMAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC9AAAAAAAAAL0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 90, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAMMAAAAAAAAAxQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADDAAAAAAAAAMMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 91, + 1, + "insert", + { + "characters": "date" + }, + "BAAAAMQAAAAAAAAAxQAAAAAAAAAAAAAAxQAAAAAAAADGAAAAAAAAAAAAAADGAAAAAAAAAMcAAAAAAAAAAAAAAMcAAAAAAAAAyAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADEAAAAAAAAAMQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 95, + 1, + "insert", + { + "characters": ";" + }, + "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADKAAAAAAAAAMoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 96, + 1, + "insert", + { + "characters": " " + }, + "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAAAAAAAAAMsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 97, + 2, + "left_delete", + null, + "AgAAAMsAAAAAAAAAywAAAAAAAAABAAAAIMoAAAAAAAAAygAAAAAAAAABAAAAOw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAAAAAAAAAMwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 98, + 1, + "insert", + { + "characters": "," + }, + "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADKAAAAAAAAAMoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 99, + 1, + "insert", + { + "characters": " " + }, + "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAAAAAAAAAMsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 104, + 1, + "paste", + null, + "AQAAAMwAAAAAAAAA6QAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAAAAAAAAAMwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 110, + 1, + "paste", + null, + "AgAAANMAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADbAAAAAAAAAAcAAABjcmVhdGVk", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAAAAAAAAANoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 114, + 1, + "insert", + { + "characters": "," + }, + "AQAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpAAAAAAAAAOkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 115, + 1, + "insert", + { + "characters": " de" + }, + "AwAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA6wAAAAAAAADsAAAAAAAAAAAAAADsAAAAAAAAAO0AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAOoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 116, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"04.9999.default\",\"filterText\":\"default\",\"kind\":6,\"data\":{\"position\":{\"line\":9,\"character\":109},\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"default=\"},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "default=" + }, + "AgAAAOsAAAAAAAAA6wAAAAAAAAACAAAAZGXrAAAAAAAAAPMAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADtAAAAAAAAAO0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 117, + 1, + "insert", + { + "characters": "None" + }, + "BAAAAPMAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD1AAAAAAAAAAAAAAD1AAAAAAAAAPYAAAAAAAAAAAAAAPYAAAAAAAAA9wAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADzAAAAAAAAAPMAAAAAAAAAAAAAAAAA8L8" + ], + [ + 127, + 1, + "", + null, + "AgAAAPMAAAAAAAAA+gAAAAAAAAAAAAAA+gAAAAAAAAD6AAAAAAAAAAQAAABOb25l", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADzAAAAAAAAAPcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 133, + 2, + "left_delete", + null, + "AgAAAOoAAAAAAAAA6gAAAAAAAAARAAAAIGRlZmF1bHQ9REVGQVVMVCnpAAAAAAAAAOkAAAAAAAAAAQAAACw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAPsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 135, + 1, + "insert", + { + "characters": "," + }, + "AQAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAOoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 136, + 1, + "insert", + { + "characters": " " + }, + "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrAAAAAAAAAOsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 141, + 1, + "paste", + null, + "AQAAAOsAAAAAAAAACQEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrAAAAAAAAAOsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 148, + 1, + "paste", + null, + "AgAAAPIAAAAAAAAA+wAAAAAAAAAAAAAA+wAAAAAAAAD7AAAAAAAAAAgAAABub3RpZmllZA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADyAAAAAAAAAPoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 153, + 1, + "insert", + { + "characters": "," + }, + "AQAAAAoBAAAAAAAACwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAKAQAAAAAAAAoBAAAAAAAAAAAAAAAA8L8" + ], + [ + 154, + 1, + "insert", + { + "characters": " " + }, + "AQAAAAsBAAAAAAAADAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAALAQAAAAAAAAsBAAAAAAAAAAAAAAAA8L8" + ], + [ + 155, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAAwBAAAAAAAADgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAA8L8" + ], + [ + 156, + 1, + "run_macro_file", + { + "file": "res://Packages/Default/Delete Left Right.sublime-macro" + }, + "AgAAAAwBAAAAAAAADAEAAAAAAAABAAAAIgwBAAAAAAAADAEAAAAAAAABAAAAIg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAANAQAAAAAAAA0BAAAAAAAAAAAAAAAA8L8" + ], + [ + 157, + 1, + "insert", + { + "characters": "Field" + }, + "BQAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAA8L8" + ], + [ + 158, + 1, + "insert", + { + "characters": "-" + }, + "AQAAABEBAAAAAAAAEgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAARAQAAAAAAABEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 159, + 1, + "left_delete", + null, + "AQAAABEBAAAAAAAAEQEAAAAAAAABAAAALQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASAQAAAAAAABIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 160, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABEBAAAAAAAAEwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAARAQAAAAAAABEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 161, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAABIBAAAAAAAAFAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASAQAAAAAAABIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 166, + 1, + "paste", + null, + "AQAAABMBAAAAAAAAHgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAATAQAAAAAAABMBAAAAAAAAAAAAAAAA8L8" + ], + [ + 171, + 1, + "insert", + { + "characters": "," + }, + "AQAAACABAAAAAAAAIQEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAgAQAAAAAAACABAAAAAAAAAAAAAAAA8L8" + ], + [ + 172, + 1, + "insert", + { + "characters": " " + }, + "AQAAACEBAAAAAAAAIgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAhAQAAAAAAACEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 173, + 1, + "paste", + null, + "AQAAACIBAAAAAAAANgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAiAQAAAAAAACIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 176, + 1, + "insert", + { + "characters": "site" + }, + "BQAAADABAAAAAAAAMQEAAAAAAAAAAAAAMQEAAAAAAAAxAQAAAAAAAAQAAABuYW1lMQEAAAAAAAAyAQAAAAAAAAAAAAAyAQAAAAAAADMBAAAAAAAAAAAAADMBAAAAAAAANAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA0AQAAAAAAADABAAAAAAAAAAAAAAAA8L8" + ], + [ + 178, + 1, + "insert", + { + "characters": ";" + }, + "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + ], + [ + 179, + 1, + "left_delete", + null, + "AQAAADYBAAAAAAAANgEAAAAAAAABAAAAOw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + ], + [ + 180, + 1, + "insert", + { + "characters": "," + }, + "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + ], + [ + 181, + 1, + "insert", + { + "characters": " " + }, + "AQAAADcBAAAAAAAAOAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + ], + [ + 182, + 1, + "paste", + null, + "AQAAADgBAAAAAAAARwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA4AQAAAAAAADgBAAAAAAAAAAAAAAAA8L8" + ], + [ + 190, + 1, + "paste", + null, + "AQAAADgBAAAAAAAATAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA4AQAAAAAAADgBAAAAAAAAAAAAAAAA8L8" + ], + [ + 192, + 1, + "cut", + null, + "AQAAAEwBAAAAAAAATAEAAAAAAAAPAAAAYXV0aG9yX2dyYXZhdGFy", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMAQAAAAAAAFsBAAAAAAAAAAAAAAAA8L8" + ], + [ + 197, + 1, + "paste", + null, + "AgAAAD8BAAAAAAAATgEAAAAAAAAAAAAATgEAAAAAAABOAQAAAAAAAAsAAABhdXRob3Jfc2l0ZQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA/AQAAAAAAAEoBAAAAAAAAAAAAAAAA8L8" + ], + [ + 201, + 1, + "insert", + { + "characters": "," + }, + "AQAAADUBAAAAAAAANgEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA1AQAAAAAAADUBAAAAAAAAAAAAAAAA8L8" + ], + [ + 202, + 1, + "insert", + { + "characters": " ef" + }, + "AwAAADYBAAAAAAAANwEAAAAAAAAAAAAANwEAAAAAAAA4AQAAAAAAAAAAAAA4AQAAAAAAADkBAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + ], + [ + 203, + 2, + "left_delete", + null, + "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAZjcBAAAAAAAANwEAAAAAAAABAAAAZQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5AQAAAAAAADkBAAAAAAAAAAAAAAAA8L8" + ], + [ + 204, + 1, + "insert", + { + "characters": "default=" + }, + "CAAAADcBAAAAAAAAOAEAAAAAAAAAAAAAOAEAAAAAAAA5AQAAAAAAAAAAAAA5AQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOwEAAAAAAAAAAAAAOwEAAAAAAAA8AQAAAAAAAAAAAAA8AQAAAAAAAD0BAAAAAAAAAAAAAD0BAAAAAAAAPgEAAAAAAAAAAAAAPgEAAAAAAAA/AQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + ], + [ + 205, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAD8BAAAAAAAAQQEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA/AQAAAAAAAD8BAAAAAAAAAAAAAAAA8L8" + ], + [ + 208, + 1, + "trim_trailing_white_space", + null, + "AQAAAFwBAAAAAAAAXAEAAAAAAAABAAAAIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABBAQAAAAAAAEEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 219, + 1, + "insert", + { + "characters": "\ndef" + }, + "BAAAAFcAAAAAAAAAWAAAAAAAAAAAAAAAWAAAAAAAAABZAAAAAAAAAAAAAABZAAAAAAAAAFoAAAAAAAAAAAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABXAAAAAAAAAFcAAAAAAAAAAAAAAACAZkA" + ], + [ + 220, + 1, + "insert", + { + "characters": " _" + }, + "AgAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABbAAAAAAAAAFsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 221, + 1, + "insert", + { + "characters": "null" + }, + "BAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 222, + 4, + "left_delete", + null, + "BAAAAGAAAAAAAAAAYAAAAAAAAAABAAAAbF8AAAAAAAAAXwAAAAAAAAABAAAAbF4AAAAAAAAAXgAAAAAAAAABAAAAdV0AAAAAAAAAXQAAAAAAAAABAAAAbg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABhAAAAAAAAAGEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 223, + 1, + "insert", + { + "characters": "empty_s" + }, + "BwAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 224, + 1, + "insert", + { + "characters": "tring" + }, + "BQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABpAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 225, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAGkAAAAAAAAAawAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABpAAAAAAAAAGkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 227, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABrAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 228, + 1, + "insert", + { + "characters": "\nreturn" + }, + "CAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABsAAAAAAAAAGwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 229, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHcAAAAAAAAAeAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB3AAAAAAAAAHcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 230, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAHgAAAAAAAAAegAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB4AAAAAAAAAHgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 232, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAHoAAAAAAAAAewAAAAAAAAAAAAAAewAAAAAAAAB/AAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB6AAAAAAAAAHoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 234, + 1, + "trim_trailing_white_space", + null, + "AQAAAHsAAAAAAAAAewAAAAAAAAAEAAAAICAgIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 242, + 1, + "insert", + { + "characters": "\n" + }, + "BAAAAPAAAAAAAAAA8QAAAAAAAAAAAAAA8QAAAAAAAAD1AAAAAAAAAAAAAADxAAAAAAAAAPEAAAAAAAAABAAAACAgICDxAAAAAAAAAPkAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 245, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAADkBAAAAAAAAOgEAAAAAAAAAAAAAOgEAAAAAAABCAQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5AQAAAAAAADkBAAAAAAAAAAAAAAAA8L8" + ], + [ + 250, + 1, + "paste", + null, + "AgAAAHUBAAAAAAAAggEAAAAAAAAAAAAAggEAAAAAAACCAQAAAAAAAAIAAAAiIg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB1AQAAAAAAAHcBAAAAAAAAAAAAAAAA8L8" + ], + [ + 252, + 1, + "trim_trailing_white_space", + null, + "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAIO8AAAAAAAAA7wAAAAAAAAABAAAAIA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAQAAAAAAAIIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 269, + 1, + "insert", + { + "characters": "," + }, + "AQAAAJoBAAAAAAAAmwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAQAAAAAAAJoBAAAAAAAAAAAAAAAA8L8" + ], + [ + 270, + 1, + "insert", + { + "characters": " " + }, + "AQAAAJsBAAAAAAAAnAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAQAAAAAAAJsBAAAAAAAAAAAAAAAA8L8" + ], + [ + 271, + 1, + "paste", + null, + "AQAAAJwBAAAAAAAAsQEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAQAAAAAAAJwBAAAAAAAAAAAAAAAA8L8" + ], + [ + 273, + 1, + "insert", + { + "characters": "," + }, + "AQAAALIBAAAAAAAAswEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAQAAAAAAALIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 274, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAALMBAAAAAAAAtAEAAAAAAAAAAAAAtAEAAAAAAAC8AQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAQAAAAAAALMBAAAAAAAAAAAAAAAA8L8" + ], + [ + 275, + 1, + "insert", + { + "characters": "Field" + }, + "BQAAALwBAAAAAAAAvQEAAAAAAAAAAAAAvQEAAAAAAAC+AQAAAAAAAAAAAAC+AQAAAAAAAL8BAAAAAAAAAAAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADBAQAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC8AQAAAAAAALwBAAAAAAAAAAAAAAAA8L8" + ], + [ + 276, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAMEBAAAAAAAAwwEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADBAQAAAAAAAMEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 277, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAMIBAAAAAAAAxAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADCAQAAAAAAAMIBAAAAAAAAAAAAAAAA8L8" + ], + [ + 278, + 1, + "insert", + { + "characters": "content" + }, + "BwAAAMMBAAAAAAAAxAEAAAAAAAAAAAAAxAEAAAAAAADFAQAAAAAAAAAAAADFAQAAAAAAAMYBAAAAAAAAAAAAAMYBAAAAAAAAxwEAAAAAAAAAAAAAxwEAAAAAAADIAQAAAAAAAAAAAADIAQAAAAAAAMkBAAAAAAAAAAAAAMkBAAAAAAAAygEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADDAQAAAAAAAMMBAAAAAAAAAAAAAAAA8L8" + ], + [ + 280, + 1, + "insert", + { + "characters": "," + }, + "AQAAAMsBAAAAAAAAzAEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAQAAAAAAAMsBAAAAAAAAAAAAAAAA8L8" + ], + [ + 281, + 1, + "insert", + { + "characters": " type\"\"" + }, + "BwAAAMwBAAAAAAAAzQEAAAAAAAAAAAAAzQEAAAAAAADOAQAAAAAAAAAAAADOAQAAAAAAAM8BAAAAAAAAAAAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA0AEAAAAAAADRAQAAAAAAAAAAAADRAQAAAAAAANIBAAAAAAAAAAAAANIBAAAAAAAA0wEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAQAAAAAAAMwBAAAAAAAAAAAAAAAA8L8" + ], + [ + 283, + 1, + "insert", + { + "characters": "=" + }, + "AQAAANEBAAAAAAAA0gEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADRAQAAAAAAANEBAAAAAAAAAAAAAAAA8L8" + ], + [ + 287, + 1, + "insert", + { + "characters": ")" + }, + "AQAAANUBAAAAAAAA1gEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADVAQAAAAAAANUBAAAAAAAAAAAAAAAA8L8" + ], + [ + 290, + 1, + "insert", + { + "characters": "text" + }, + "BAAAANMBAAAAAAAA1AEAAAAAAAAAAAAA1AEAAAAAAADVAQAAAAAAAAAAAADVAQAAAAAAANYBAAAAAAAAAAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAQAAAAAAANMBAAAAAAAAAAAAAAAA8L8" + ], + [ + 305, + 1, + "insert", + { + "characters": "confiu" + }, + "BwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAUAAABzZXR1cIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 306, + 1, + "left_delete", + null, + "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAdQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACGAAAAAAAAAIYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 307, + 1, + "insert", + { + "characters": "gure" + }, + "BAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAAAAAAAAAIUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 5, + 1, + "left_delete", + null, + "AQAAAG0BAAAAAAAAbQEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", + "AQAAAAAAAAABAAAAbQEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "left_delete", + null, + "AQAAAIcBAAAAAAAAhwEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", + "AQAAAAAAAAABAAAAhwEAAAAAAACeAQAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 3, + "left_delete", + null, + "AwAAAFgAAAAAAAAAWAAAAAAAAAAiAAAAZGVmIF9lbXB0eV9zdHJpbmcoKToKICAgIHJldHVybiAiIlcAAAAAAAAAVwAAAAAAAAABAAAAClYAAAAAAAAAVgAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAegAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" + ], + [ + 20, + 1, + "insert", + { + "characters": "time" + }, + "BAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "insert", + { + "characters": "time" + }, + "BAAAABgBAAAAAAAAGQEAAAAAAAAAAAAAGQEAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAABsBAAAAAAAAHAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGAEAAAAAAAAYAQAAAAAAAAAAAAAAIINA" + ], + [ + 26, + 1, + "insert", + { + "characters": "time" + }, + "BAAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" + ], + [ + 29, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAACZAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkYiA9IE5vbmUKCmRlZiBjb25maWd1cmUoZGJfdXJpKToKICAgIGRiID0gREFMKGRiX3VyaSkKICAgIGRiLmRlZmluZV90YWJsZSgiY29tbWVudCIsIEZpZWxkKCJ1cmwiKSwgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJub3RpZmllZCIsIHR5cGU9ImRhdGV0aW1lIiksRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLCBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgRmllbGQoImNvbnRlbnQiLCB0eXBlPSJ0ZXh0IikpCgAAAAAAAAAA0gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 36, + 1, + "insert", + { + "characters": "dal" + }, + "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABTAAAAAAAAAAQAAABOb25lUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + ], + [ + 37, + 3, + "left_delete", + null, + "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", + "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 38, + 1, + "insert", + { + "characters": "dal" + }, + "AwAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgAAAAAAAABSAAAAAAAAAAAAAAAAAPC/" + ], + [ + 39, + 3, + "left_delete", + null, + "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", + "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 40, + 1, + "insert", + { + "characters": "DAL)" + }, + "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAFUAAAAAAAAAVgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgAAAAAAAABSAAAAAAAAAAAAAAAAAPC/" + ], + [ + 41, + 1, + "left_delete", + null, + "AQAAAFUAAAAAAAAAVQAAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + ], + [ + 42, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAFUAAAAAAAAAVwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 56, + 1, + "insert", + { + "characters": "\ndb." + }, + "BQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAjAAAAAAAAACNAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 57, + 1, + "insert", + { + "characters": "c" + }, + "AQAAAI0AAAAAAAAAjgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 58, + 1, + "insert", + { + "characters": "on" + }, + "AgAAAI4AAAAAAAAAjwAAAAAAAAAAAAAAjwAAAAAAAACQAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjgAAAAAAAACOAAAAAAAAAAAAAAAAAPC/" + ], + [ + 59, + 2, + "left_delete", + null, + "AgAAAI8AAAAAAAAAjwAAAAAAAAABAAAAbo4AAAAAAAAAjgAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" + ], + [ + 62, + 1, + "left_delete", + null, + "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYg", + "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + ], + [ + 63, + 1, + "insert", + { + "characters": "atabase" + }, + "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + ], + [ + 71, + 1, + "paste", + null, + "AgAAAHsAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACDAAAAAAAAAAIAAABkYg", + "AQAAAAAAAAABAAAAewAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + ], + [ + 75, + 1, + "paste", + null, + "AgAAAJ8AAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACnAAAAAAAAAAIAAABkYg", + "AQAAAAAAAAABAAAAoQAAAAAAAACfAAAAAAAAAAAAAAAAAPC/" + ], + [ + 79, + 1, + "insert", + { + "characters": "None" + }, + "BQAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABZAAAAAAAAAAUAAABEQUwoKVkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWAAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" + ], + [ + 83, + 1, + "insert", + { + "characters": "db_dal" + }, + "BwAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABOAAAAAAAAAAgAAABkYXRhYmFzZU4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 94, + 1, + "paste", + null, + "AgAAAHgAAAAAAAAAfgAAAAAAAAAAAAAAfgAAAAAAAAB+AAAAAAAAAAgAAABkYXRhYmFzZQ", + "AQAAAAAAAAABAAAAeAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 98, + 1, + "right_delete", + null, + "AQAAAJEAAAAAAAAAkQAAAAAAAAAEAAAAZGIuYw", + "AQAAAAAAAAABAAAAlQAAAAAAAACRAAAAAAAAAAAAAAAAAEJA" + ], + [ + 100, + 1, + "left_delete", + null, + "AQAAAIwAAAAAAAAAjAAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAAAA" + ], + [ + 104, + 1, + "paste", + null, + "AgAAAJUAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACbAAAAAAAAAAgAAABkYXRhYmFzZQ", + "AQAAAAAAAAABAAAAlQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" + ], + [ + 106, + 1, + "trim_trailing_white_space", + null, + "AQAAAIwAAAAAAAAAjAAAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAmwAAAAAAAACbAAAAAAAAAAAAAAAAAPC/" + ], + [ + 114, + 1, + "insert", + { + "characters": "\ndb_dal." + }, + "CQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 122, + 1, + "insert", + { + "characters": "DAL" + }, + "BAAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABXAAAAAAAAAAQAAABOb25lVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVgAAAAAAAABaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 123, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAFkAAAAAAAAAWwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + ], + [ + 130, + 1, + "left_delete", + null, + "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 131, + 1, + "insert", + { + "characters": "." + }, + "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 135, + 1, + "right_delete", + null, + "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAgAAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 136, + 1, + "insert", + { + "characters": "." + }, + "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 142, + 1, + "insert", + { + "characters": "#" + }, + "AQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 152, + 1, + "left_delete", + null, + "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 153, + 1, + "insert", + { + "characters": "." + }, + "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 154, + 1, + "insert", + { + "characters": "set" + }, + "AwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 155, + 3, + "left_delete", + null, + "AwAAAIIAAAAAAAAAggAAAAAAAAABAAAAdIEAAAAAAAAAgQAAAAAAAAABAAAAZYAAAAAAAAAAgAAAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 156, + 1, + "insert", + { + "characters": "ur" + }, + "AgAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 157, + 3, + "left_delete", + null, + "AwAAAIEAAAAAAAAAgQAAAAAAAAABAAAAcoAAAAAAAAAAgAAAAAAAAAABAAAAdX8AAAAAAAAAfwAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAggAAAAAAAACCAAAAAAAAAAAAAAAAAPC/" + ], + [ + 158, + 1, + "insert", + { + "characters": ".co" + }, + "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 159, + 1, + "insert", + { + "characters": "n" + }, + "AQAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAggAAAAAAAACCAAAAAAAAAAAAAAAAAPC/" + ], + [ + 171, + 1, + "insert", + { + "characters": "None" + }, + "BAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAAWwAAAAAAAABcAAAAAAAAAAAAAABcAAAAAAAAAF0AAAAAAAAAAAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWgAAAAAAAABaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 181, + 1, + "left_delete", + null, + "AQAAAFoAAAAAAAAAWgAAAAAAAAAEAAAATm9uZQ", + "AQAAAAAAAAABAAAAWgAAAAAAAABeAAAAAAAAAAAAAAAAAPC/" + ], + [ + 189, + 1, + "insert", + { + "characters": "global" + }, + "BwAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB6AAAAAAAAAAoAAABkYl9kYWwuY29uegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwAAAAAAAAB5AAAAAAAAAAAAAAAAAPC/" + ], + [ + 190, + 1, + "insert", + { + "characters": " db_dal" + }, + "BwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 193, + 1, + "right_delete", + null, + "AQAAAIsAAAAAAAAAiwAAAAAAAAABAAAAIw", + "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAEJA" + ], + [ + 203, + 3, + "right_delete", + null, + "AwAAAE0AAAAAAAAATQAAAAAAAAAPAAAAZGJfZGFsID0gREFMKCkKTQAAAAAAAABNAAAAAAAAAAEAAAAKTQAAAAAAAABNAAAAAAAAAAEAAAAK", + "AQAAAAAAAAABAAAAXAAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 206, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADgAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkZWYgY29uZmlndXJlKGRiX3VyaSk6CiAgICBnbG9iYWwgZGJfZGFsCiAgICBkYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAiY29tbWVudCIsCiAgICAgICAgRmllbGQoInVybCIpLAogICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJwdWJsaXNoZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfZ3JhdmF0YXIiKSwKICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICkKAAAAAAAAAADhAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 214, + 1, + "insert", + { + "characters": "db" + }, + "AgAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 215, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAE8AAAAAAAAAUAAAAAAAAAAAAAAAUAAAAAAAAABRAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + ], + [ + 216, + 3, + "left_delete", + null, + "AwAAAFAAAAAAAAAAUAAAAAAAAAABAAAAPU8AAAAAAAAATwAAAAAAAAABAAAAIE4AAAAAAAAATgAAAAAAAAABAAAAYg", + "AQAAAAAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 217, + 1, + "insert", + { + "characters": "a" + }, + "AQAAAE4AAAAAAAAATwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + ], + [ + 218, + 1, + "left_delete", + null, + "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYQ", + "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + ], + [ + 219, + 1, + "insert", + { + "characters": "atabase" + }, + "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + ], + [ + 220, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 221, + 1, + "insert", + { + "characters": " " + }, + "AQAAAFcAAAAAAAAAWAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAAPC/" + ], + [ + 223, + 1, + "insert", + { + "characters": "Databas" + }, + "BwAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAABdAAAAAAAAAF4AAAAAAAAAAAAAAF4AAAAAAAAAXwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWAAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" + ], + [ + 224, + 1, + "insert", + { + "characters": "e" + }, + "AQAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXwAAAAAAAABfAAAAAAAAAAAAAAAAAPC/" + ], + [ + 225, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAGAAAAAAAAAAYgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 227, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAGIAAAAAAAAAYwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYgAAAAAAAABiAAAAAAAAAAAAAAAAAPC/" + ], + [ + 230, + 1, + "insert", + { + "characters": "\nclass" + }, + "BgAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAGYAAAAAAAAAZwAAAAAAAAAAAAAAZwAAAAAAAABoAAAAAAAAAAAAAABoAAAAAAAAAGkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAAAA" + ], + [ + 231, + 1, + "insert", + { + "characters": " Database" + }, + "CQAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" + ], + [ + 232, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAHIAAAAAAAAAdAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" + ], + [ + 234, + 2, + "left_delete", + null, + "AgAAAHMAAAAAAAAAcwAAAAAAAAABAAAAKXIAAAAAAAAAcgAAAAAAAAABAAAAKA", + "AQAAAAAAAAABAAAAdAAAAAAAAAB0AAAAAAAAAAAAAAAAAPC/" + ], + [ + 235, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" + ], + [ + 236, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 243, + 1, + "indent", + null, + "DgAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAlAAAAAAAAACYAAAAAAAAAAAAAACqAAAAAAAAAK4AAAAAAAAAAAAAAMcAAAAAAAAAywAAAAAAAAAAAAAA5AAAAAAAAADoAAAAAAAAAAAAAAD7AAAAAAAAAP8AAAAAAAAAAAAAABUBAAAAAAAAGQEAAAAAAAAAAAAARAEAAAAAAABIAQAAAAAAAAAAAAB0AQAAAAAAAHgBAAAAAAAAAAAAAKUBAAAAAAAAqQEAAAAAAAAAAAAAxwEAAAAAAADLAQAAAAAAAAAAAADpAQAAAAAAAO0BAAAAAAAAAAAAAA8CAAAAAAAAEwIAAAAAAAAAAAAAOgIAAAAAAAA+AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAeQAAAAAAAAAMAgAAAAAAAAAAAAAAAAAA" + ], + [ + 250, + 1, + "insert", + { + "characters": "self," + }, + "BgAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" + ], + [ + 254, + 1, + "insert", + { + "characters": "\n\ndef" + }, + "CAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgeQAAAAAAAAB6AAAAAAAAAAAAAAB6AAAAAAAAAHsAAAAAAAAAAAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 255, + 1, + "insert", + { + "characters": " __init" + }, + "BwAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 256, + 1, + "insert", + { + "characters": "__" + }, + "AgAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 257, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAIUAAAAAAAAAhwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 259, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 260, + 1, + "insert", + { + "characters": "\n" + }, + "AwAAAIgAAAAAAAAAiQAAAAAAAAAAAAAAiQAAAAAAAACNAAAAAAAAAAAAAACNAAAAAAAAAJEAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" + ], + [ + 261, + 1, + "insert", + { + "characters": "slef" + }, + "BAAAAJEAAAAAAAAAkgAAAAAAAAAAAAAAkgAAAAAAAACTAAAAAAAAAAAAAACTAAAAAAAAAJQAAAAAAAAAAAAAAJQAAAAAAAAAlQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 262, + 3, + "left_delete", + null, + "AwAAAJQAAAAAAAAAlAAAAAAAAAABAAAAZpMAAAAAAAAAkwAAAAAAAAABAAAAZZIAAAAAAAAAkgAAAAAAAAABAAAAbA", + "AQAAAAAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 263, + 1, + "insert", + { + "characters": "elf" + }, + "AwAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" + ], + [ + 267, + 1, + "right_delete", + null, + "AQAAAHUAAAAAAAAAdQAAAAAAAAAhAAAAICAgIGRlZiBfX2luaXRfXygpOgogICAgICAgIHNlbGYK", + "AQAAAAAAAAABAAAAdQAAAAAAAACWAAAAAAAAAAAAAAAAAAAA" + ], + [ + 270, + 1, + "reindent", + null, + "AQAAAHUAAAAAAAAAeQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdQAAAAAAAAB1AAAAAAAAAAAAAAAAAAAA" + ], + [ + 271, + 1, + "insert", + { + "characters": "db" + }, + "AgAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAeQAAAAAAAAB5AAAAAAAAAAAAAAAAAPC/" + ], + [ + 272, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" + ], + [ + 273, + 2, + "left_delete", + null, + "AgAAAHwAAAAAAAAAfAAAAAAAAAABAAAAPXsAAAAAAAAAewAAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAfQAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + ], + [ + 274, + 1, + "insert", + { + "characters": "_dal" + }, + "BAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" + ], + [ + 275, + 1, + "left_delete", + null, + "AQAAAH4AAAAAAAAAfgAAAAAAAAABAAAAbA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 276, + 1, + "insert", + { + "characters": "l" + }, + "AQAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" + ], + [ + 277, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":9,\"character\":10},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "db_dal" + }, + "AgAAAHkAAAAAAAAAeQAAAAAAAAAGAAAAZGJfZGFseQAAAAAAAAB/AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 278, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 281, + 1, + "insert", + { + "characters": " =" + }, + "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAQAAAAgICAggAAAAAAAAACBAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 282, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQAAAAAAAACBAAAAAAAAAAAAAAAAAPC/" + ], + [ + 291, + 1, + "right_delete", + null, + "AQAAALAAAAAAAAAAsAAAAAAAAAANAAAAZ2xvYmFsIGRiX2RhbA", + "AQAAAAAAAAABAAAAsAAAAAAAAAC9AAAAAAAAAP///////+9/" + ], + [ + 293, + 1, + "left_delete", + null, + "AQAAAKcAAAAAAAAApwAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAqAAAAAAAAACoAAAAAAAAAAAAAAAAAAAA" + ], + [ + 296, + 1, + "insert", + { + "characters": "sl" + }, + "AgAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuAAAAAAAAAC4AAAAAAAAAAAAAAAAAFJA" + ], + [ + 297, + 1, + "left_delete", + null, + "AQAAALkAAAAAAAAAuQAAAAAAAAABAAAAbA", + "AQAAAAAAAAABAAAAugAAAAAAAAC6AAAAAAAAAAAAAAAAAPC/" + ], + [ + 298, + 1, + "insert", + { + "characters": "elf." + }, + "BAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuQAAAAAAAAC5AAAAAAAAAAAAAAAAAPC/" + ], + [ + 301, + 1, + "insert", + { + "characters": "self." + }, + "BQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 303, + 1, + "trim_trailing_white_space", + null, + "AQAAAKcAAAAAAAAApwAAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + ], + [ + 309, + 1, + "cut", + null, + "AQAAAE0AAAAAAAAATQAAAAAAAAAVAAAAZGF0YWJhc2UgPSBEYXRhYmFzZSgp", + "AQAAAAAAAAABAAAAYgAAAAAAAABNAAAAAAAAAAAAAAAAAAAA" + ], + [ + 310, + 2, + "right_delete", + null, + "AgAAAE0AAAAAAAAATQAAAAAAAAABAAAACk0AAAAAAAAATQAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 316, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAADUCAAAAAAAANgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAAAA" + ], + [ + 317, + 1, + "paste", + null, + "AQAAADYCAAAAAAAASwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANgIAAAAAAAA2AgAAAAAAAAAAAAAAAPC/" + ], + [ + 321, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAABLAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpjbGFzcyBEYXRhYmFzZToKCiAgICBkYl9kYWwgPSBOb25lCgogICAgZGVmIGNvbmZpZ3VyZShzZWxmLGRiX3VyaSk6CiAgICAgICAgc2VsZi5kYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgICAgIHNlbGYuZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAgICAgImNvbW1lbnQiLAogICAgICAgICAgICBGaWVsZCgidXJsIiksCiAgICAgICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoIm5vdGlmaWVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX3NpdGUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9ncmF2YXRhciIpLAogICAgICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICAgICApCgpkYXRhYmFzZSA9IERhdGFiYXNlKCkAAAAAAAAAAE8CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAoGdA" + ], + [ + 336, + 1, + "insert", + { + "characters": "Dal" + }, + "BAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAgAAABEYXRhYmFzZVUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" + ], + [ + 340, + 1, + "insert", + { + "characters": "Db" + }, + "AwAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAMAAABEYWxVAAAAAAAAAFYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVwAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" + ], + [ + 341, + 2, + "left_delete", + null, + "AgAAAFUAAAAAAAAAVQAAAAAAAAABAAAAYlQAAAAAAAAAVAAAAAAAAAABAAAARA", + "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + ], + [ + 342, + 1, + "insert", + { + "characters": "Db" + }, + "AgAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABWAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" + ], + [ + 343, + 1, + "insert", + { + "characters": "Dal" + }, + "AwAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + ], + [ + 355, + 1, + "paste", + null, + "AgAAAEECAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABGAgAAAAAAAAgAAABEYXRhYmFzZQ", + "AQAAAAAAAAABAAAAQQIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + ], + [ + 369, + 1, + "insert", + { + "characters": "Databse" + }, + "CAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAUAAABEYkRhbFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAFgAAAAAAAAAAAAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + ], + [ + 370, + 2, + "left_delete", + null, + "AgAAAFoAAAAAAAAAWgAAAAAAAAABAAAAZVkAAAAAAAAAWQAAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAAWwAAAAAAAABbAAAAAAAAAAAAAAAAAPC/" + ], + [ + 371, + 1, + "insert", + { + "characters": "ase" + }, + "AwAAAFkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + ], + [ + 381, + 1, + "paste", + null, + "AgAAAEQCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABMAgAAAAAAAAUAAABEYkRhbA", + "AQAAAAAAAAABAAAARAIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + ], + [ + 386, + 1, + "insert", + { + "characters": "db" + }, + "AwAAADkCAAAAAAAAOgIAAAAAAAAAAAAAOgIAAAAAAAA6AgAAAAAAAAgAAABkYXRhYmFzZToCAAAAAAAAOwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQQIAAAAAAAA5AgAAAAAAAAAAAAAAAPC/" + ], + [ + 401, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + ], + [ + 402, + 1, + "reindent", + null, + "AQAAADgCAAAAAAAAQAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" + ], + [ + 403, + 1, + "left_delete", + null, + "AQAAADwCAAAAAAAAPAIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 404, + 1, + "insert", + { + "characters": "def" + }, + "AwAAADwCAAAAAAAAPQIAAAAAAAAAAAAAPQIAAAAAAAA+AgAAAAAAAAAAAAA+AgAAAAAAAD8CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPAIAAAAAAAA8AgAAAAAAAAAAAAAAAPC/" + ], + [ + 405, + 1, + "insert", + { + "characters": " " + }, + "AQAAAD8CAAAAAAAAQAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAAAPC/" + ], + [ + 406, + 1, + "insert", + { + "characters": "dal" + }, + "AwAAAEACAAAAAAAAQQIAAAAAAAAAAAAAQQIAAAAAAABCAgAAAAAAAAAAAABCAgAAAAAAAEMCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 407, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAEMCAAAAAAAARQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" + ], + [ + 410, + 1, + "insert", + { + "characters": "self" + }, + "BAAAAEQCAAAAAAAARQIAAAAAAAAAAAAARQIAAAAAAABGAgAAAAAAAAAAAABGAgAAAAAAAEcCAAAAAAAAAAAAAEcCAAAAAAAASAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARAIAAAAAAABEAgAAAAAAAAAAAAAAAPC/" + ], + [ + 412, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAEkCAAAAAAAASgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASQIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + ], + [ + 413, + 1, + "insert", + { + "characters": "\nreturn" + }, + "CQAAAEoCAAAAAAAASwIAAAAAAAAAAAAASwIAAAAAAABPAgAAAAAAAAAAAABPAgAAAAAAAFMCAAAAAAAAAAAAAFMCAAAAAAAAVAIAAAAAAAAAAAAAVAIAAAAAAABVAgAAAAAAAAAAAABVAgAAAAAAAFYCAAAAAAAAAAAAAFYCAAAAAAAAVwIAAAAAAAAAAAAAVwIAAAAAAABYAgAAAAAAAAAAAABYAgAAAAAAAFkCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASgIAAAAAAABKAgAAAAAAAAAAAAAAAPC/" + ], + [ + 414, + 1, + "insert", + { + "characters": " self." + }, + "BgAAAFkCAAAAAAAAWgIAAAAAAAAAAAAAWgIAAAAAAABbAgAAAAAAAAAAAABbAgAAAAAAAFwCAAAAAAAAAAAAAFwCAAAAAAAAXQIAAAAAAAAAAAAAXQIAAAAAAABeAgAAAAAAAAAAAABeAgAAAAAAAF8CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWQIAAAAAAABZAgAAAAAAAAAAAAAAAPC/" + ], + [ + 415, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0000.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":25,\"character\":20},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "db_dal" + }, + "AQAAAF8CAAAAAAAAZQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXwIAAAAAAABfAgAAAAAAAAAAAAAAAPC/" + ], + [ + 420, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAAB3AgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKZGIgPSBEYXRhYmFzZSgpCgAAAAAAAAAAeAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZgIAAAAAAABmAgAAAAAAAAAAAAAAQG1A" + ], + [ + 437, + 1, + "insert", + { + "characters": "database" + }, + "CQAAAGgCAAAAAAAAaQIAAAAAAAAAAAAAaQIAAAAAAABpAgAAAAAAAAIAAABkYmkCAAAAAAAAagIAAAAAAAAAAAAAagIAAAAAAABrAgAAAAAAAAAAAABrAgAAAAAAAGwCAAAAAAAAAAAAAGwCAAAAAAAAbQIAAAAAAAAAAAAAbQIAAAAAAABuAgAAAAAAAAAAAABuAgAAAAAAAG8CAAAAAAAAAAAAAG8CAAAAAAAAcAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaAIAAAAAAABqAgAAAAAAAAAAAAAAAPC/" + ], + [ + 439, + 1, + "insert", + { + "characters": "db" + }, + "AgAAAH4CAAAAAAAAfwIAAAAAAAAAAAAAfwIAAAAAAACAAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgIAAAAAAAB+AgAAAAAAAAAAAAAAAFJA" + ], + [ + 440, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 441, + 1, + "insert", + { + "characters": " data" + }, + "BQAAAIICAAAAAAAAgwIAAAAAAAAAAAAAgwIAAAAAAACEAgAAAAAAAAAAAACEAgAAAAAAAIUCAAAAAAAAAAAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAggIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + ], + [ + 442, + 1, + "insert", + { + "characters": "base" + }, + "BAAAAIcCAAAAAAAAiAIAAAAAAAAAAAAAiAIAAAAAAACJAgAAAAAAAAAAAACJAgAAAAAAAIoCAAAAAAAAAAAAAIoCAAAAAAAAiwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwIAAAAAAACHAgAAAAAAAAAAAAAAAPC/" + ], + [ + 443, + 1, + "insert", + { + "characters": "." + }, + "AQAAAIsCAAAAAAAAjAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + ], + [ + 444, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0000.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":29,\"character\":14},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "db_dal" + }, + "AQAAAIwCAAAAAAAAkgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjAIAAAAAAACMAgAAAAAAAAAAAAAAAPC/" + ], + [ + 460, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAACSAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKCmRhdGFiYXNlID0gRGF0YWJhc2UoKQpkYiA9IGRhdGFiYXNlLmRiX2RhbAAAAAAAAAAAkwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgIAAAAAAACSAgAAAAAAAAAAAAAAAPC/" + ], + [ + 469, + 1, + "insert", + { + "characters": "DAL" + }, + "BAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABtAAAAAAAAAAQAAABOb25lbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbAAAAAAAAABwAAAAAAAAAAAAAAAAAPC/" + ], + [ + 470, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAG8AAAAAAAAAcQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbwAAAAAAAABvAAAAAAAAAAAAAAAAAPC/" + ], + [ + 482, + 2, + "left_delete", + null, + "AgAAADgCAAAAAAAAOAIAAAAAAAAuAAAACiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbDcCAAAAAAAANwIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAZgIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" + ], + [ + 503, + 1, + "insert", + { + "characters": "\n\n" + }, + "BQAAADcCAAAAAAAAOAIAAAAAAAAAAAAAOAIAAAAAAABAAgAAAAAAAAAAAABAAgAAAAAAAEECAAAAAAAAAAAAAEECAAAAAAAASQIAAAAAAAAAAAAAOAIAAAAAAAA4AgAAAAAAAAgAAAAgICAgICAgIA", + "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + ], + [ + 504, + 1, + "left_delete", + null, + "AQAAAD0CAAAAAAAAPQIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAQQIAAAAAAABBAgAAAAAAAAAAAAAAAPC/" + ], + [ + 505, + 1, + "insert", + { + "characters": "def" + }, + "AwAAAD0CAAAAAAAAPgIAAAAAAAAAAAAAPgIAAAAAAAA/AgAAAAAAAAAAAAA/AgAAAAAAAEACAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPQIAAAAAAAA9AgAAAAAAAAAAAAAAAPC/" + ], + [ + 506, + 1, + "insert", + { + "characters": " " + }, + "AQAAAEACAAAAAAAAQQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 507, + 1, + "insert", + { + "characters": "get" + }, + "AwAAAEECAAAAAAAAQgIAAAAAAAAAAAAAQgIAAAAAAABDAgAAAAAAAAAAAABDAgAAAAAAAEQCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQQIAAAAAAABBAgAAAAAAAAAAAAAAAPC/" + ], + [ + 508, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAEQCAAAAAAAARgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARAIAAAAAAABEAgAAAAAAAAAAAAAAAPC/" + ], + [ + 509, + 1, + "insert", + { + "characters": "self" + }, + "BAAAAEUCAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABHAgAAAAAAAAAAAABHAgAAAAAAAEgCAAAAAAAAAAAAAEgCAAAAAAAASQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARQIAAAAAAABFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 511, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAEoCAAAAAAAASwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASgIAAAAAAABKAgAAAAAAAAAAAAAAAPC/" + ], + [ + 512, + 1, + "insert", + { + "characters": "\nreturn" + }, + "CQAAAEsCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABQAgAAAAAAAAAAAABQAgAAAAAAAFQCAAAAAAAAAAAAAFQCAAAAAAAAVQIAAAAAAAAAAAAAVQIAAAAAAABWAgAAAAAAAAAAAABWAgAAAAAAAFcCAAAAAAAAAAAAAFcCAAAAAAAAWAIAAAAAAAAAAAAAWAIAAAAAAABZAgAAAAAAAAAAAABZAgAAAAAAAFoCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASwIAAAAAAABLAgAAAAAAAAAAAAAAAPC/" + ], + [ + 513, + 1, + "insert", + { + "characters": " sek" + }, + "BAAAAFoCAAAAAAAAWwIAAAAAAAAAAAAAWwIAAAAAAABcAgAAAAAAAAAAAABcAgAAAAAAAF0CAAAAAAAAAAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWgIAAAAAAABaAgAAAAAAAAAAAAAAAPC/" + ], + [ + 514, + 1, + "insert", + { + "characters": "f" + }, + "AQAAAF4CAAAAAAAAXwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXgIAAAAAAABeAgAAAAAAAAAAAAAAAPC/" + ], + [ + 515, + 2, + "left_delete", + null, + "AgAAAF4CAAAAAAAAXgIAAAAAAAABAAAAZl0CAAAAAAAAXQIAAAAAAAABAAAAaw", + "AQAAAAAAAAABAAAAXwIAAAAAAABfAgAAAAAAAAAAAAAAAPC/" + ], + [ + 516, + 1, + "insert", + { + "characters": "lf.d" + }, + "BAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAGACAAAAAAAAYQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXQIAAAAAAABdAgAAAAAAAAAAAAAAAPC/" + ], + [ + 517, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0001.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":25,\"character\":21},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "db_dal" + }, + "AgAAAGACAAAAAAAAYAIAAAAAAAABAAAAZGACAAAAAAAAZgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYQIAAAAAAABhAgAAAAAAAAAAAAAAAPC/" + ], + [ + 524, + 1, + "insert", + { + "characters": "get" + }, + "BAAAAI0CAAAAAAAAjgIAAAAAAAAAAAAAjgIAAAAAAACOAgAAAAAAAAYAAABkYl9kYWyOAgAAAAAAAI8CAAAAAAAAAAAAAI8CAAAAAAAAkAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQIAAAAAAACTAgAAAAAAAAAAAAAAAPC/" + ], + [ + 525, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAAPC/" + ], + [ + 530, + 1, + "right_delete", + null, + "AQAAAJACAAAAAAAAkAIAAAAAAAACAAAAKCk", + "AQAAAAAAAAABAAAAkAIAAAAAAACSAgAAAAAAAP///////+9/" + ], + [ + 537, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAIGNA" + ], + [ + 549, + 1, + "left_delete", + null, + "AQAAAIACAAAAAAAAgAIAAAAAAAABAAAAYg", + "AQAAAAAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAPC/" + ], + [ + 550, + 1, + "insert", + { + "characters": "al" + }, + "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 552, + 2, + "right_delete", + null, + "AgAAAJECAAAAAAAAkQIAAAAAAAABAAAAKJECAAAAAAAAkQIAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAAkQIAAAAAAACRAgAAAAAAAAAAAAAAAPC/" + ], + [ + 559, + 1, + "insert", + { + "characters": "db" + }, + "AwAAAH8CAAAAAAAAgAIAAAAAAAAAAAAAgAIAAAAAAACAAgAAAAAAAAMAAABkYWyAAgAAAAAAAIECAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfwIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + ], + [ + 577, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAH4CAAAAAAAAfwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgIAAAAAAAB+AgAAAAAAAAAAAAAAAPC/" + ], + [ + 581, + 1, + "insert", + { + "characters": "al" + }, + "AwAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACCAgAAAAAAAAEAAABiggIAAAAAAACDAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + ], + [ + 583, + 1, + "left_delete", + null, + "AQAAAH4CAAAAAAAAfgIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAfwIAAAAAAAB/AgAAAAAAAAAAAAAAADtA" + ], + [ + 590, + 2, + "left_delete", + null, + "AgAAAIECAAAAAAAAgQIAAAAAAAABAAAAbIACAAAAAAAAgAIAAAAAAAABAAAAYQ", + "AQAAAAAAAAABAAAAggIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + ], + [ + 591, + 1, + "insert", + { + "characters": "b" + }, + "AQAAAIACAAAAAAAAgQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 600, + 1, + "", + null, + "AQAAALQAAAAAAAAAwQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" + ], + [ + 602, + 1, + "insert", + { + "characters": "," + }, + "AQAAALQAAAAAAAAAtQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" + ], + [ + 603, + 1, + "insert", + { + "characters": " " + }, + "AQAAALUAAAAAAAAAtgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtQAAAAAAAAC1AAAAAAAAAAAAAAAAAPC/" + ], + [ + 612, + 1, + "insert", + { + "characters": "True" + }, + "BQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAUAAABGYWxzZb8AAAAAAAAAwAAAAAAAAAAAAAAAwAAAAAAAAADBAAAAAAAAAAAAAADBAAAAAAAAAMIAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 623, + 1, + "insert", + { + "characters": "False" + }, + "BgAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAQAAABUcnVlvwAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAMEAAAAAAAAAAAAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADDAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "stacosys/run.py", + "settings": + { + "buffer_size": 2472, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "toggle_breakpoint", + null, + "AQAAAIUFAAAAAAAAhQUAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGI3MDRhOWFjIC8vCg", + "AQAAAAAAAAABAAAAjAUAAAAAAACMBQAAAAAAAAAAAAAAAPC/" + ], + [ + 15, + 1, + "toggle_breakpoint", + null, + "AQAAAIUFAAAAAAAAvwUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgUAAAAAAACSBQAAAAAAAAAAAAAAAPC/" + ], + [ + 25, + 1, + "toggle_breakpoint", + null, + "AQAAAIUFAAAAAAAAhQUAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDQwOGIxMTUzIC8vCg", + "AQAAAAAAAAABAAAAnwUAAAAAAACfBQAAAAAAAAAAAAAAAPC/" + ], + [ + 28, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADQCQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBhcmdwYXJzZQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHN5cwoKZnJvbSBzdGFjb3N5cy5kYiBpbXBvcnQgZGF0YWJhc2UsIGRhbwpmcm9tIHN0YWNvc3lzLmludGVyZmFjZSBpbXBvcnQgYXBpLCBhcHAsIGZvcm0KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2Uud2ViIGltcG9ydCBhZG1pbgpmcm9tIHN0YWNvc3lzLnNlcnZpY2UgaW1wb3J0IGNvbmZpZywgbWFpbGVyLCByc3MKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgoKCiMgY29uZmlndXJlIGxvZ2dpbmcKZGVmIGNvbmZpZ3VyZV9sb2dnaW5nKGxldmVsKToKICAgIHJvb3RfbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoKQogICAgcm9vdF9sb2dnZXIuc2V0TGV2ZWwobGV2ZWwpCiAgICBoYW5kbGVyID0gbG9nZ2luZy5TdHJlYW1IYW5kbGVyKCkKICAgIGhhbmRsZXIuc2V0TGV2ZWwobGV2ZWwpCiAgICBmb3JtYXR0ZXIgPSBsb2dnaW5nLkZvcm1hdHRlcigiWyUoYXNjdGltZSlzXSAlKG5hbWUpcyAlKGxldmVsbmFtZSlzICUobWVzc2FnZSlzIikKICAgIGhhbmRsZXIuc2V0Rm9ybWF0dGVyKGZvcm1hdHRlcikKICAgIHJvb3RfbG9nZ2VyLmFkZEhhbmRsZXIoaGFuZGxlcikKCgpkZWYgc3RhY29zeXNfc2VydmVyKGNvbmZpZ19wYXRobmFtZSk6CiAgICAjIGNvbmZpZ3VyZSBsb2dnaW5nCiAgICBsb2dnZXIgPSBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXykKICAgIGNvbmZpZ3VyZV9sb2dnaW5nKGxvZ2dpbmcuSU5GTykKICAgIGxvZ2dpbmcuZ2V0TG9nZ2VyKCJ3ZXJremV1ZyIpLmxldmVsID0gbG9nZ2luZy5XQVJOSU5HCgogICAgIyBjaGVjayBjb25maWcgZmlsZSBleGlzdHMKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShjb25maWdfcGF0aG5hbWUpOgogICAgICAgIGxvZ2dlci5lcnJvcigiQ29uZmlndXJhdGlvbiBmaWxlICclcycgbm90IGZvdW5kLiIsIGNvbmZpZ19wYXRobmFtZSkKICAgICAgICBzeXMuZXhpdCgxKQoKICAgICMgbG9hZCBhbmQgY2hlY2sgY29uZmlnCiAgICBjb25maWcubG9hZChjb25maWdfcGF0aG5hbWUpCiAgICBpc19jb25maWdfb2ssIGVycmV1cl9jb25maWcgPSBjb25maWcuY2hlY2soKQogICAgaWYgbm90IGlzX2NvbmZpZ19vazoKICAgICAgICBsb2dnZXIuZXJyb3IoIkNvbmZpZ3VyYXRpb24gaW5jb3JyZWN0ZSAnJXMnIiwgZXJyZXVyX2NvbmZpZykKICAgICAgICBzeXMuZXhpdCgxKQogICAgbG9nZ2VyLmluZm8oY29uZmlnKQoKICAgICMgaW5pdGlhbGl6ZSBkYXRhYmFzZQogICAgZGF0YWJhc2UuY29uZmlndXJlKGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkRCKSkKCiAgICByID0gZGFvLmZpbmRfY29tbWVudF9ieV9pZCgxKQoKICAgIGxvZ2dlci5pbmZvKCJTdGFydCBTdGFjb3N5cyBhcHBsaWNhdGlvbiIpCgogICAgIyBnZW5lcmF0ZSBSU1MKICAgIHJzcy5jb25maWd1cmUoCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuUlNTX0ZJTEUpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfTkFNRSksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9QUk9UTyksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQogICAgcnNzLmdlbmVyYXRlKCkKCiAgICAjIGNvbmZpZ3VyZSBtYWlsZXIKICAgIG1haWxlci5jb25maWd1cmVfc210cCgKICAgICAgICBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TTVRQX0hPU1QpLAogICAgICAgIGNvbmZpZy5nZXRfaW50KENvbmZpZ1BhcmFtZXRlci5TTVRQX1BPUlQpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfTE9HSU4pLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfUEFTU1dPUkQpLAogICAgKQogICAgbWFpbGVyLmNvbmZpZ3VyZV9kZXN0aW5hdGlvbihjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TSVRFX0FETUlOX0VNQUlMKSkKICAgIG1haWxlci5jaGVjaygpCgogICAgbG9nZ2VyLmluZm8oInN0YXJ0IGludGVyZmFjZXMgJXMgJXMgJXMiLCBhcGksIGZvcm0sIGFkbWluKQoKICAgICMgc3RhcnQgRmxhc2sKICAgIGFwcC5ydW4oCiAgICAgICAgaG9zdD1jb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5IVFRQX0hPU1QpLAogICAgICAgIHBvcnQ9Y29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLkhUVFBfUE9SVCksCiAgICAgICAgZGVidWc9RmFsc2UsCiAgICAgICAgdXNlX3JlbG9hZGVyPUZhbHNlLAogICAgKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcigpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCJjb25maWciLCBoZWxwPSJjb25maWcgcGF0aCBuYW1lIikKICAgIGFyZ3MgPSBwYXJzZXIucGFyc2VfYXJncygpCiAgICBzdGFjb3N5c19zZXJ2ZXIoYXJncy5jb25maWcpCgAAAAAAAAAA0AkAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQUAAAAAAACFBQAAAAAAAAAAAAAAAPC/" + ], + [ + 33, + 1, + "right_delete", + null, + "AQAAAGEFAAAAAAAAYQUAAAAAAAAjAAAACiAgICByID0gZGFvLmZpbmRfY29tbWVudF9ieV9pZCgxKQo", + "AQAAAAAAAAABAAAAYQUAAAAAAACEBQAAAAAAAAAAAAAAkHJA" + ], + [ + 43, + 1, + "right_delete", + null, + "AQAAAHgAAAAAAAAAeAAAAAAAAAAFAAAAZGFvLCA", + "AQAAAAAAAAABAAAAeAAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "config-dev.ini", + "settings": + { + "buffer_size": 657, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 5, + 1, + "insert", + { + "characters": "\nsq" + }, + "AwAAAEUAAAAAAAAARgAAAAAAAAAAAAAARgAAAAAAAABHAAAAAAAAAAAAAABHAAAAAAAAAEgAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABFAAAAAAAAAEUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 6, + 2, + "left_delete", + null, + "AgAAAEcAAAAAAAAARwAAAAAAAAABAAAAcUYAAAAAAAAARgAAAAAAAAABAAAAcw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABIAAAAAAAAAEgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 7, + 1, + "insert", + { + "characters": "dbçsq" + }, + "BQAAAEYAAAAAAAAARwAAAAAAAAAAAAAARwAAAAAAAABIAAAAAAAAAAAAAABIAAAAAAAAAEkAAAAAAAAAAAAAAEkAAAAAAAAASgAAAAAAAAAAAAAASgAAAAAAAABLAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABGAAAAAAAAAEYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 8, + 3, + "left_delete", + null, + "AwAAAEoAAAAAAAAASgAAAAAAAAABAAAAcUkAAAAAAAAASQAAAAAAAAABAAAAc0gAAAAAAAAASAAAAAAAAAACAAAAw6c", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 9, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAEgAAAAAAAAASQAAAAAAAAAAAAAASQAAAAAAAABKAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABIAAAAAAAAAEgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 10, + 1, + "insert", + { + "characters": " " + }, + "AQAAAEoAAAAAAAAASwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABKAAAAAAAAAEoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 12, + 1, + "", + null, + "AQAAAEsAAAAAAAAAXgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 18, + 1, + "paste", + null, + "AgAAAFQAAAAAAAAAXQAAAAAAAAAAAAAAXQAAAAAAAABdAAAAAAAAAAoAAABzdG9yYWdlLmRi", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABUAAAAAAAAAF4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 25, + 1, + "insert", + { + "characters": "2" + }, + "AQAAAFYAAAAAAAAAVwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABWAAAAAAAAAFYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 5, + 1, + "left_delete", + null, + "AQAAAFYAAAAAAAAAVgAAAAAAAAABAAAAMg", + "AQAAAAAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "right_delete", + null, + "AQAAACsAAAAAAAAAKwAAAAAAAAAaAAAAZGJfc3FsaXRlX2ZpbGUgPSBkYi5zcWxpdGU", + "AQAAAAAAAAABAAAARQAAAAAAAAArAAAAAAAAAAAAAAAAAAAA" + ], + [ + 10, + 1, + "left_delete", + null, + "AQAAACoAAAAAAAAAKgAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAKwAAAAAAAAArAAAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 1, + "insert", + { + "characters": "2" + }, + "AQAAADsAAAAAAAAAPAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOwAAAAAAAAA7AAAAAAAAAAAAAAAAAPC/" + ], + [ + 14, + 1, + "left_delete", + null, + "AQAAADsAAAAAAAAAOwAAAAAAAAABAAAAMg", + "AQAAAAAAAAABAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "dbmigration/migrate_from_3.3_to_4.0.py", + "settings": + { + "buffer_size": 1022, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 12, + 1, + "right_delete", + null, + "AQAAAPMBAAAAAAAA8wEAAAAAAAADAAAAVkFS", + "AQAAAAAAAAABAAAA8wEAAAAAAAD2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 1, + "insert", + { + "characters": "512" + }, + "BAAAAPgBAAAAAAAA+QEAAAAAAAAAAAAA+QEAAAAAAAD5AQAAAAAAAAMAAAAyNTX5AQAAAAAAAPoBAAAAAAAAAAAAAPoBAAAAAAAA+wEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+AEAAAAAAAD7AQAAAAAAAAAAAAAAAPC/" + ], + [ + 21, + 1, + "right_delete", + null, + "AQAAAP0BAAAAAAAA/QEAAAAAAAAIAAAATk9UIE5VTEw", + "AQAAAAAAAAABAAAA/QEAAAAAAAAFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "right_delete", + null, + "AQAAAPwBAAAAAAAA/AEAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAA/AEAAAAAAAD8AQAAAAAAAAAAAAAAAPC/" + ], + [ + 27, + 1, + "insert", + { + "characters": "TIMESTAM" + }, + "CQAAAAsCAAAAAAAADAIAAAAAAAAAAAAADAIAAAAAAAAMAgAAAAAAAAgAAABEQVRFVElNRQwCAAAAAAAADQIAAAAAAAAAAAAADQIAAAAAAAAOAgAAAAAAAAAAAAAOAgAAAAAAAA8CAAAAAAAAAAAAAA8CAAAAAAAAEAIAAAAAAAAAAAAAEAIAAAAAAAARAgAAAAAAAAAAAAARAgAAAAAAABICAAAAAAAAAAAAABICAAAAAAAAEwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACwIAAAAAAAATAgAAAAAAAAAAAAAAAPC/" + ], + [ + 28, + 1, + "insert", + { + "characters": "P" + }, + "AQAAABMCAAAAAAAAFAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEwIAAAAAAAATAgAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "paste", + null, + "AgAAACICAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAArAgAAAAAAABEAAABEQVRFVElNRSBOT1QgTlVMTA", + "AQAAAAAAAAABAAAAIgIAAAAAAAAzAgAAAAAAAAAAAAAAAPC/" + ], + [ + 40, + 1, + "paste", + null, + "AgAAADsCAAAAAAAARAIAAAAAAAAAAAAARAIAAAAAAABEAgAAAAAAAAgAAABEQVRFVElNRQ", + "AQAAAAAAAAABAAAAOwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" + ], + [ + 49, + 1, + "paste", + null, + "AgAAAFYCAAAAAAAAXwIAAAAAAAAAAAAAXwIAAAAAAABfAgAAAAAAABUAAABWQVJDSEFSKDI1NSkgTk9UIE5VTEw", + "AQAAAAAAAAABAAAAVgIAAAAAAABrAgAAAAAAAAAAAAAAAPC/" + ], + [ + 52, + 1, + "paste", + null, + "AgAAAHECAAAAAAAAegIAAAAAAAAAAAAAegIAAAAAAAB6AgAAAAAAABUAAABWQVJDSEFSKDI1NSkgTk9UIE5VTEw", + "AQAAAAAAAAABAAAAcQIAAAAAAACGAgAAAAAAAAAAAAAAAPC/" + ], + [ + 55, + 1, + "paste", + null, + "AgAAAJACAAAAAAAAmQIAAAAAAAAAAAAAmQIAAAAAAACZAgAAAAAAAAwAAAB2YXJjaGFyKDI1NSk", + "AQAAAAAAAAABAAAAkAIAAAAAAACcAgAAAAAAAAAAAAAAAPC/" + ], + [ + 60, + 1, + "left_delete", + null, + "AQAAAKsCAAAAAAAAqwIAAAAAAAAJAAAAIE5PVCBOVUxM", + "AQAAAAAAAAABAAAAqwIAAAAAAAC0AgAAAAAAAAAAAAAAAPC/" + ], + [ + 66, + 1, + "right_delete", + null, + "AQAAAHIBAAAAAAAAcgEAAAAAAAAQAAAARFJPUCBUQUJMRSBzaXRlOw", + "AQAAAAAAAAABAAAAggEAAAAAAAByAQAAAAAAAAAAAAAAAAAA" + ], + [ + 67, + 1, + "left_delete", + null, + "AQAAAHEBAAAAAAAAcQEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAcgEAAAAAAAByAQAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "stacosys/db/dao.py", + "settings": + { + "buffer_size": 2026, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 4, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAALwAAAAAAAAAvQAAAAAAAAAAAAAAvQAAAAAAAADBAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 7, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BgAAAEgAAAAAAAAASQAAAAAAAAAAAAAAvgAAAAAAAAC+AAAAAAAAAAQAAAAgICAgSQAAAAAAAABKAAAAAAAAAAAAAABKAAAAAAAAAEsAAAAAAAAAAAAAAEsAAAAAAAAATAAAAAAAAAAAAAAATAAAAAAAAABNAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASAAAAAAAAABIAAAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "insert", + { + "characters": " stacosys.db" + }, + "DAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAAUQAAAAAAAABSAAAAAAAAAAAAAABSAAAAAAAAAFMAAAAAAAAAAAAAAFMAAAAAAAAAVAAAAAAAAAAAAAAAVAAAAAAAAABVAAAAAAAAAAAAAABVAAAAAAAAAFYAAAAAAAAAAAAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "insert", + { + "characters": " imo" + }, + "BAAAAFkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAFwAAAAAAAAAXQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + ], + [ + 10, + 1, + "left_delete", + null, + "AQAAAFwAAAAAAAAAXAAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert", + { + "characters": "port" + }, + "BAAAAFwAAAAAAAAAXQAAAAAAAAAAAAAAXQAAAAAAAABeAAAAAAAAAAAAAABeAAAAAAAAAF8AAAAAAAAAAAAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" + ], + [ + 12, + 1, + "insert", + { + "characters": " " + }, + "AQAAAGAAAAAAAAAAYQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 18, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.db\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":4,\"character\":24},\"symbolLabel\":\"db\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "db" + }, + "AQAAAGEAAAAAAAAAYwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYQAAAAAAAABhAAAAAAAAAAAAAAAAAPC/" + ], + [ + 27, + 1, + "reindent", + null, + "AQAAANgAAAAAAAAA3AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" + ], + [ + 28, + 1, + "insert", + { + "characters": "db." + }, + "AwAAANwAAAAAAAAA3QAAAAAAAAAAAAAA3QAAAAAAAADeAAAAAAAAAAAAAADeAAAAAAAAAN8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3AAAAAAAAADcAAAAAAAAAAAAAAAAAPC/" + ], + [ + 29, + 1, + "left_delete", + null, + "AQAAAN4AAAAAAAAA3gAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + ], + [ + 30, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAN4AAAAAAAAA4AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAAPC/" + ], + [ + 33, + 1, + "right_delete", + null, + "AQAAAN4AAAAAAAAA3gAAAAAAAAACAAAAKCk", + "AQAAAAAAAAABAAAA3gAAAAAAAADgAAAAAAAAAP///////+9/" + ], + [ + 34, + 1, + "insert", + { + "characters": "." + }, + "AQAAAN4AAAAAAAAA3wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "insert", + { + "characters": "com" + }, + "AwAAAN8AAAAAAAAA4AAAAAAAAAAAAAAA4AAAAAAAAADhAAAAAAAAAAAAAADhAAAAAAAAAOIAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + ], + [ + 36, + 1, + "insert", + { + "characters": "m" + }, + "AQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" + ], + [ + 37, + 1, + "insert", + { + "characters": "ent." + }, + "BAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" + ], + [ + 38, + 1, + "left_delete", + null, + "AQAAAOYAAAAAAAAA5gAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAA5wAAAAAAAADnAAAAAAAAAAAAAAAAAPC/" + ], + [ + 39, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAOYAAAAAAAAA6AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5gAAAAAAAADmAAAAAAAAAAAAAAAAAPC/" + ], + [ + 47, + 1, + "paste", + null, + "AQAAAOcAAAAAAAAA8QAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5wAAAAAAAADnAAAAAAAAAAAAAAAAAPC/" + ], + [ + 65, + 1, + "insert", + { + "characters": "return" + }, + "BgAAANwAAAAAAAAA3QAAAAAAAAAAAAAA3QAAAAAAAADeAAAAAAAAAAAAAADeAAAAAAAAAN8AAAAAAAAAAAAAAN8AAAAAAAAA4AAAAAAAAAAAAAAA4AAAAAAAAADhAAAAAAAAAAAAAADhAAAAAAAAAOIAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3AAAAAAAAADcAAAAAAAAAAAAAAAAAPC/" + ], + [ + 66, + 1, + "insert", + { + "characters": " " + }, + "AQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" + ], + [ + 69, + 1, + "insert", + { + "characters": "#" + }, + "AQAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/gAAAAAAAAD+AAAAAAAAAAAAAAAAAEJA" + ], + [ + 72, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAACvBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKVElNRV9GT1JNQVQgPSAiJVktJW0tJWQgJUg6JU06JVMiCgoKZGVmIGZpbmRfY29tbWVudF9ieV9pZChjb21tZW50X2lkKToKICAgIHJldHVybiBkYi5jb21tZW50KGNvbW1lbnRfaWQpCiAgICAjcmV0dXJuIENvbW1lbnQuZ2V0X2J5X2lkKGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5ub3RpZmllZCA9IGRhdGV0aW1lLm5vdygpLnN0cmZ0aW1lKFRJTUVfRk9STUFUKQogICAgY29tbWVudC5zYXZlKCkKCgpkZWYgcHVibGlzaF9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5wdWJsaXNoZWQgPSBkYXRldGltZS5ub3coKS5zdHJmdGltZShUSU1FX0ZPUk1BVCkKICAgIGNvbW1lbnQuc2F2ZSgpCgoKZGVmIGRlbGV0ZV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5kZWxldGVfaW5zdGFuY2UoKQoKCmRlZiBmaW5kX25vdF9ub3RpZmllZF9jb21tZW50cygpOgogICAgcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5ub3RpZmllZC5pc19udWxsKCkpCgoKZGVmIGZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbCgpKQoKCmRlZiBmaW5kX3B1Ymxpc2hlZF9jb21tZW50c19ieV91cmwodXJsKToKICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5vcmRlcl9ieSgrQ29tbWVudC5wdWJsaXNoZWQpCiAgICApCgoKZGVmIGNvdW50X3B1Ymxpc2hlZF9jb21tZW50cyh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBDb21tZW50LnNlbGVjdChDb21tZW50KQogICAgICAgIC53aGVyZSgoQ29tbWVudC51cmwgPT0gdXJsKSAmIChDb21tZW50LnB1Ymxpc2hlZC5pc19udWxsKEZhbHNlKSkpCiAgICAgICAgLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIENvbW1lbnQuc2VsZWN0KENvbW1lbnQpLndoZXJlKENvbW1lbnQucHVibGlzaGVkLmlzX251bGwoRmFsc2UpKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY3JlYXRlZCA9IGRhdGV0aW1lLm5vdygpLnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIpCiAgICBjb21tZW50ID0gQ29tbWVudCgKICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9Y3JlYXRlZCwKICAgICAgICBub3RpZmllZD1Ob25lLAogICAgICAgIHB1Ymxpc2hlZD1Ob25lLAogICAgKQogICAgY29tbWVudC5zYXZlKCkKICAgIHJldHVybiBjb21tZW50CgAAAAAAAAAAsAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 77, + 1, + "insert", + { + "characters": "#" + }, + "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAAAA" + ], + [ + 93, + 1, + "right_delete", + null, + "AQAAAGQAAAAAAAAAZAAAAAAAAAABAAAAIw", + "AQAAAAAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAAAA" + ], + [ + 99, + 1, + "toggle_breakpoint", + null, + "AQAAANgAAAAAAAAAEgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 109, + 1, + "insert", + { + "characters": "()" + }, + "AgAAAB8BAAAAAAAAIAEAAAAAAAAAAAAAIAEAAAAAAAAhAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHwEAAAAAAAAfAQAAAAAAAAAAAAAAAPC/" + ], + [ + 117, + 1, + "toggle_breakpoint", + null, + "AQAAANgAAAAAAAAA2AAAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDg4MzY3Y2ZlIC8vCg", + "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" + ], + [ + 133, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACFAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 134, + 1, + "insert", + { + "characters": "returb" + }, + "BgAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAACHAgAAAAAAAIgCAAAAAAAAAAAAAIgCAAAAAAAAiQIAAAAAAAAAAAAAiQIAAAAAAACKAgAAAAAAAAAAAACKAgAAAAAAAIsCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQIAAAAAAACFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 135, + 1, + "insert", + { + "characters": " " + }, + "AQAAAIsCAAAAAAAAjAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + ], + [ + 136, + 2, + "left_delete", + null, + "AgAAAIsCAAAAAAAAiwIAAAAAAAABAAAAIIoCAAAAAAAAigIAAAAAAAABAAAAYg", + "AQAAAAAAAAABAAAAjAIAAAAAAACMAgAAAAAAAAAAAAAAAPC/" + ], + [ + 137, + 1, + "insert", + { + "characters": "n" + }, + "AQAAAIoCAAAAAAAAiwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAigIAAAAAAACKAgAAAAAAAAAAAAAAAPC/" + ], + [ + 138, + 1, + "insert", + { + "characters": " db" + }, + "AwAAAIsCAAAAAAAAjAIAAAAAAAAAAAAAjAIAAAAAAACNAgAAAAAAAAAAAACNAgAAAAAAAI4CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + ], + [ + 139, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAI4CAAAAAAAAkAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjgIAAAAAAACOAgAAAAAAAAAAAAAAAPC/" + ], + [ + 141, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAAPC/" + ], + [ + 150, + 1, + "right_delete", + null, + "AQAAAIUCAAAAAAAAhQIAAAAAAAANAAAAcmV0dXJuIGRiKCkoKQ", + "AQAAAAAAAAABAAAAkgIAAAAAAACFAgAAAAAAAAAAAAAAAEJA" + ], + [ + 152, + 1, + "left_delete", + null, + "AQAAAIACAAAAAAAAgAIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAAAA" + ], + [ + 154, + 1, + "trim_trailing_white_space", + null, + "AQAAAIACAAAAAAAAgAIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + ], + [ + 160, + 1, + "right_delete", + null, + "AQAAAAABAAAAAAAAAAEAAAAAAAAmAAAAIyByZXR1cm4gQ29tbWVudC5nZXRfYnlfaWQoY29tbWVudF9pZCk", + "AQAAAAAAAAABAAAAJgEAAAAAAAAAAQAAAAAAAAAAAAAAAEJA" + ], + [ + 162, + 1, + "left_delete", + null, + "AQAAAPsAAAAAAAAA+wAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAAAA" + ], + [ + 164, + 1, + "trim_trailing_white_space", + null, + "AQAAAPsAAAAAAAAA+wAAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAA+wAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" + ], + [ + 193, + 1, + "right_delete", + null, + "AQAAAEkBAAAAAAAASQEAAAAAAAAWAAAALnN0cmZ0aW1lKFRJTUVfRk9STUFUKQ", + "AQAAAAAAAAABAAAASQEAAAAAAABfAQAAAAAAAP///////+9/" + ], + [ + 196, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAA0HRA" + ], + [ + 197, + 1, + "insert", + { + "characters": "db" + }, + "AgAAAE4BAAAAAAAATwEAAAAAAAAAAAAATwEAAAAAAABQAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgEAAAAAAABOAQAAAAAAAAAAAAAAAPC/" + ], + [ + 198, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAFABAAAAAAAAUgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUAEAAAAAAABQAQAAAAAAAAAAAAAAAPC/" + ], + [ + 200, + 1, + "insert", + { + "characters": ".inser" + }, + "BgAAAFIBAAAAAAAAUwEAAAAAAAAAAAAAUwEAAAAAAABUAQAAAAAAAAAAAABUAQAAAAAAAFUBAAAAAAAAAAAAAFUBAAAAAAAAVgEAAAAAAAAAAAAAVgEAAAAAAABXAQAAAAAAAAAAAABXAQAAAAAAAFgBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" + ], + [ + 201, + 5, + "left_delete", + null, + "BQAAAFcBAAAAAAAAVwEAAAAAAAABAAAAclYBAAAAAAAAVgEAAAAAAAABAAAAZVUBAAAAAAAAVQEAAAAAAAABAAAAc1QBAAAAAAAAVAEAAAAAAAABAAAAblMBAAAAAAAAUwEAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAAWAEAAAAAAABYAQAAAAAAAAAAAAAAAPC/" + ], + [ + 202, + 1, + "insert", + { + "characters": "comment.i" + }, + "CQAAAFMBAAAAAAAAVAEAAAAAAAAAAAAAVAEAAAAAAABVAQAAAAAAAAAAAABVAQAAAAAAAFYBAAAAAAAAAAAAAFYBAAAAAAAAVwEAAAAAAAAAAAAAVwEAAAAAAABYAQAAAAAAAAAAAABYAQAAAAAAAFkBAAAAAAAAAAAAAFkBAAAAAAAAWgEAAAAAAAAAAAAAWgEAAAAAAABbAQAAAAAAAAAAAABbAQAAAAAAAFwBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUwEAAAAAAABTAQAAAAAAAAAAAAAAAPC/" + ], + [ + 203, + 1, + "insert", + { + "characters": "ns" + }, + "AgAAAFwBAAAAAAAAXQEAAAAAAAAAAAAAXQEAAAAAAABeAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXAEAAAAAAABcAQAAAAAAAAAAAAAAAPC/" + ], + [ + 204, + 1, + "insert", + { + "characters": "ert-" + }, + "BAAAAF4BAAAAAAAAXwEAAAAAAAAAAAAAXwEAAAAAAABgAQAAAAAAAAAAAABgAQAAAAAAAGEBAAAAAAAAAAAAAGEBAAAAAAAAYgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXgEAAAAAAABeAQAAAAAAAAAAAAAAAPC/" + ], + [ + 205, + 1, + "left_delete", + null, + "AQAAAGEBAAAAAAAAYQEAAAAAAAABAAAALQ", + "AQAAAAAAAAABAAAAYgEAAAAAAABiAQAAAAAAAAAAAAAAAPC/" + ], + [ + 206, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAGEBAAAAAAAAYwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYQEAAAAAAABhAQAAAAAAAAAAAAAAAPC/" + ], + [ + 211, + 1, + "paste", + null, + "AQAAAEgAAAAAAAAAaQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASAAAAAAAAABIAAAAAAAAAAAAAAAAAPC/" + ], + [ + 213, + 1, + "insert", + { + "characters": "asd" + }, + "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAkAAABkYXRhY2xhc3NhAAAAAAAAAGIAAAAAAAAAAAAAAGIAAAAAAAAAYwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaQAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 214, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"05.0000.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":3,\"character\":27},\"symbolLabel\":\"asdict\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "asdict" + }, + "AgAAAGAAAAAAAAAAYAAAAAAAAAADAAAAYXNkYAAAAAAAAABmAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAPC/" + ], + [ + 217, + 1, + "insert", + { + "characters": "asd" + }, + "AwAAAIABAAAAAAAAgQEAAAAAAAAAAAAAgQEAAAAAAACCAQAAAAAAAAAAAACCAQAAAAAAAIMBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAAPC/" + ], + [ + 218, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"05.0000.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":16,\"character\":27},\"symbolLabel\":\"asdict\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "asdict" + }, + "AgAAAIABAAAAAAAAgAEAAAAAAAADAAAAYXNkgAEAAAAAAACGAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwEAAAAAAACDAQAAAAAAAAAAAAAAAPC/" + ], + [ + 219, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAIYBAAAAAAAAiAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" + ], + [ + 220, + 1, + "insert", + { + "characters": "comment" + }, + "BwAAAIcBAAAAAAAAiAEAAAAAAAAAAAAAiAEAAAAAAACJAQAAAAAAAAAAAACJAQAAAAAAAIoBAAAAAAAAAAAAAIoBAAAAAAAAiwEAAAAAAAAAAAAAiwEAAAAAAACMAQAAAAAAAAAAAACMAQAAAAAAAI0BAAAAAAAAAAAAAI0BAAAAAAAAjgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" + ], + [ + 225, + 1, + "right_delete", + null, + "AQAAAJUBAAAAAAAAlQEAAAAAAAAOAAAAY29tbWVudC5zYXZlKCk", + "AQAAAAAAAAABAAAAowEAAAAAAACVAQAAAAAAAAAAAAAAAEJA" + ], + [ + 227, + 1, + "left_delete", + null, + "AQAAAJABAAAAAAAAkAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAkQEAAAAAAACRAQAAAAAAAAAAAAAAAAAA" + ], + [ + 230, + 1, + "trim_trailing_white_space", + null, + "AQAAAJABAAAAAAAAkAEAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAlQEAAAAAAACVAQAAAAAAAAAAAAAAgHZA" + ], + [ + 235, + 1, + "right_delete", + null, + "AQAAAOABAAAAAAAA4AEAAAAAAAAWAAAALnN0cmZ0aW1lKFRJTUVfRk9STUFUKQ", + "AQAAAAAAAAABAAAA4AEAAAAAAAD2AQAAAAAAAP///////+9/" + ], + [ + 239, + 1, + "right_delete", + null, + "AQAAAK4AAAAAAAAArgAAAAAAAAAhAAAAVElNRV9GT1JNQVQgPSAiJVktJW0tJWQgJUg6JU06JVMi", + "AQAAAAAAAAABAAAAzwAAAAAAAACuAAAAAAAAAAAAAAAAAAAA" + ], + [ + 240, + 2, + "left_delete", + null, + "AgAAAK0AAAAAAAAArQAAAAAAAAABAAAACqwAAAAAAAAArAAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAArgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" + ], + [ + 251, + 1, + "paste", + null, + "AgAAAMIBAAAAAAAA5gEAAAAAAAAAAAAA5gEAAAAAAADmAQAAAAAAAA4AAABjb21tZW50LnNhdmUoKQ", + "AQAAAAAAAAABAAAAwgEAAAAAAADQAQAAAAAAAAAAAAAAAPC/" + ], + [ + 261, + 1, + "insert", + { + "characters": "\ndb" + }, + "BAAAAA4CAAAAAAAADwIAAAAAAAAAAAAADwIAAAAAAAATAgAAAAAAAAAAAAATAgAAAAAAABQCAAAAAAAAAAAAABQCAAAAAAAAFQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADgIAAAAAAAAOAgAAAAAAAAAAAAAAAPC/" + ], + [ + 262, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABUCAAAAAAAAFwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFQIAAAAAAAAVAgAAAAAAAAAAAAAAAPC/" + ], + [ + 264, + 1, + "insert", + { + "characters": "." + }, + "AQAAABcCAAAAAAAAGAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFwIAAAAAAAAXAgAAAAAAAAAAAAAAAPC/" + ], + [ + 265, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABgCAAAAAAAAGgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" + ], + [ + 266, + 1, + "run_macro_file", + { + "file": "res://Packages/Default/Delete Left Right.sublime-macro" + }, + "AgAAABgCAAAAAAAAGAIAAAAAAAABAAAAKBgCAAAAAAAAGAIAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAAGQIAAAAAAAAZAgAAAAAAAAAAAAAAAPC/" + ], + [ + 267, + 1, + "left_delete", + null, + "AQAAABcCAAAAAAAAFwIAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" + ], + [ + 268, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABcCAAAAAAAAGQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFwIAAAAAAAAXAgAAAAAAAAAAAAAAAPC/" + ], + [ + 269, + 1, + "insert", + { + "characters": "db" + }, + "AgAAABgCAAAAAAAAGQIAAAAAAAAAAAAAGQIAAAAAAAAaAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" + ], + [ + 270, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABoCAAAAAAAAHAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGgIAAAAAAAAaAgAAAAAAAAAAAAAAAPC/" + ], + [ + 272, + 1, + "insert", + { + "characters": ".comment." + }, + "CQAAABwCAAAAAAAAHQIAAAAAAAAAAAAAHQIAAAAAAAAeAgAAAAAAAAAAAAAeAgAAAAAAAB8CAAAAAAAAAAAAAB8CAAAAAAAAIAIAAAAAAAAAAAAAIAIAAAAAAAAhAgAAAAAAAAAAAAAhAgAAAAAAACICAAAAAAAAAAAAACICAAAAAAAAIwIAAAAAAAAAAAAAIwIAAAAAAAAkAgAAAAAAAAAAAAAkAgAAAAAAACUCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHAIAAAAAAAAcAgAAAAAAAAAAAAAAAPC/" + ], + [ + 273, + 1, + "insert", + { + "characters": "id" + }, + "AgAAACUCAAAAAAAAJgIAAAAAAAAAAAAAJgIAAAAAAAAnAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJQIAAAAAAAAlAgAAAAAAAAAAAAAAAPC/" + ], + [ + 275, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAACcCAAAAAAAAKAIAAAAAAAAAAAAAKAIAAAAAAAApAgAAAAAAAAAAAAApAgAAAAAAACoCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJwIAAAAAAAAnAgAAAAAAAAAAAAAAAPC/" + ], + [ + 276, + 1, + "insert", + { + "characters": " comment." + }, + "CQAAACoCAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAAsAgAAAAAAAAAAAAAsAgAAAAAAAC0CAAAAAAAAAAAAAC0CAAAAAAAALgIAAAAAAAAAAAAALgIAAAAAAAAvAgAAAAAAAAAAAAAvAgAAAAAAADACAAAAAAAAAAAAADACAAAAAAAAMQIAAAAAAAAAAAAAMQIAAAAAAAAyAgAAAAAAAAAAAAAyAgAAAAAAADMCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKgIAAAAAAAAqAgAAAAAAAAAAAAAAAPC/" + ], + [ + 277, + 1, + "insert", + { + "characters": "id" + }, + "AgAAADMCAAAAAAAANAIAAAAAAAAAAAAANAIAAAAAAAA1AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMwIAAAAAAAAzAgAAAAAAAAAAAAAAAPC/" + ], + [ + 278, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.id\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":23,\"character\":38},\"symbolLabel\":\"id\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"id\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "id" + }, + "AgAAADMCAAAAAAAAMwIAAAAAAAACAAAAaWQzAgAAAAAAADUCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAPC/" + ], + [ + 282, + 1, + "insert", + { + "characters": ".de" + }, + "AwAAADYCAAAAAAAANwIAAAAAAAAAAAAANwIAAAAAAAA4AgAAAAAAAAAAAAA4AgAAAAAAADkCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANgIAAAAAAAA2AgAAAAAAAAAAAAAA8HVA" + ], + [ + 283, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.delete\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":23,\"character\":42},\"symbolLabel\":\"delete\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"delete\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "delete" + }, + "AgAAADcCAAAAAAAANwIAAAAAAAACAAAAZGU3AgAAAAAAAD0CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOQIAAAAAAAA5AgAAAAAAAAAAAAAAAPC/" + ], + [ + 284, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAD0CAAAAAAAAPwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPQIAAAAAAAA9AgAAAAAAAAAAAAAAAPC/" + ], + [ + 287, + 1, + "insert", + { + "characters": "_" + }, + "AQAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + ], + [ + 295, + 1, + "left_delete", + null, + "AQAAADcCAAAAAAAANwIAAAAAAAABAAAAXw", + "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAEHdA" + ], + [ + 298, + 1, + "right_delete", + null, + "AQAAAEQCAAAAAAAARAIAAAAAAAAZAAAAY29tbWVudC5kZWxldGVfaW5zdGFuY2UoKQ", + "AQAAAAAAAAABAAAAXQIAAAAAAABEAgAAAAAAAAAAAAAAAEJA" + ], + [ + 300, + 1, + "left_delete", + null, + "AQAAAD8CAAAAAAAAPwIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAAAA" + ], + [ + 302, + 1, + "trim_trailing_white_space", + null, + "AQAAAD8CAAAAAAAAPwIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAAAPC/" + ], + [ + 309, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAGMCAAAAAAAAZAIAAAAAAAAAAAAAZAIAAAAAAABoAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwIAAAAAAABjAgAAAAAAAAAAAAAAAPC/" + ], + [ + 310, + 1, + "paste", + null, + "AQAAAGgCAAAAAAAAiwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaAIAAAAAAABoAgAAAAAAAAAAAAAAAPC/" + ], + [ + 312, + 1, + "insert", + { + "characters": "return" + }, + "BgAAAGgCAAAAAAAAaQIAAAAAAAAAAAAAaQIAAAAAAABqAgAAAAAAAAAAAABqAgAAAAAAAGsCAAAAAAAAAAAAAGsCAAAAAAAAbAIAAAAAAAAAAAAAbAIAAAAAAABtAgAAAAAAAAAAAABtAgAAAAAAAG4CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaAIAAAAAAABoAgAAAAAAAAAAAAAAAEJA" + ], + [ + 313, + 1, + "insert", + { + "characters": " " + }, + "AQAAAG4CAAAAAAAAbwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbgIAAAAAAABuAgAAAAAAAAAAAAAAAPC/" + ], + [ + 319, + 1, + "insert", + { + "characters": "notified" + }, + "CQAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACCAgAAAAAAAAIAAABpZIICAAAAAAAAgwIAAAAAAAAAAAAAgwIAAAAAAACEAgAAAAAAAAAAAACEAgAAAAAAAIUCAAAAAAAAAAAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAACHAgAAAAAAAIgCAAAAAAAAAAAAAIgCAAAAAAAAiQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQIAAAAAAACDAgAAAAAAAAAAAAAAAPC/" + ], + [ + 322, + 1, + "insert", + { + "characters": "None" + }, + "BQAAAI0CAAAAAAAAjgIAAAAAAAAAAAAAjgIAAAAAAACOAgAAAAAAAAcAAABjb21tZW50jgIAAAAAAACPAgAAAAAAAAAAAACPAgAAAAAAAJACAAAAAAAAAAAAAJACAAAAAAAAkQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQIAAAAAAACUAgAAAAAAAAAAAAAAAPC/" + ], + [ + 324, + 1, + "right_delete", + null, + "AQAAAJECAAAAAAAAkQIAAAAAAAADAAAALmlk", + "AQAAAAAAAAABAAAAkQIAAAAAAACUAgAAAAAAAAAAAAAAAPC/" + ], + [ + 326, + 1, + "insert", + { + "characters": ".se" + }, + "AwAAAJICAAAAAAAAkwIAAAAAAAAAAAAAkwIAAAAAAACUAgAAAAAAAAAAAACUAgAAAAAAAJUCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgIAAAAAAACSAgAAAAAAAAAAAAAAAPC/" + ], + [ + 327, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.select\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":27,\"character\":49},\"symbolLabel\":\"select\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"select\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "select" + }, + "AgAAAJMCAAAAAAAAkwIAAAAAAAACAAAAc2WTAgAAAAAAAJkCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlQIAAAAAAACVAgAAAAAAAAAAAAAAAPC/" + ], + [ + 328, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAJkCAAAAAAAAmwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAmQIAAAAAAACZAgAAAAAAAAAAAAAAAPC/" + ], + [ + 333, + 1, + "right_delete", + null, + "AQAAAKACAAAAAAAAoAIAAAAAAAA5AAAAcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5ub3RpZmllZC5pc19udWxsKCkp", + "AQAAAAAAAAABAAAAoAIAAAAAAADZAgAAAAAAAP///////+9/" + ], + [ + 335, + 1, + "left_delete", + null, + "AQAAAJsCAAAAAAAAmwIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAnAIAAAAAAACcAgAAAAAAAAAAAAAAAAAA" + ], + [ + 337, + 1, + "trim_trailing_white_space", + null, + "AQAAAJsCAAAAAAAAmwIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAmwIAAAAAAACbAgAAAAAAAAAAAAAAAPC/" + ], + [ + 344, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAMACAAAAAAAAwQIAAAAAAAAAAAAAwQIAAAAAAADFAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwAIAAAAAAADAAgAAAAAAAP///////+9/" + ], + [ + 345, + 1, + "paste", + null, + "AQAAAMUCAAAAAAAA+AIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQIAAAAAAADFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 351, + 1, + "insert", + { + "characters": "published" + }, + "CgAAAN4CAAAAAAAA3wIAAAAAAAAAAAAA3wIAAAAAAADfAgAAAAAAAAgAAABub3RpZmllZN8CAAAAAAAA4AIAAAAAAAAAAAAA4AIAAAAAAADhAgAAAAAAAAAAAADhAgAAAAAAAOICAAAAAAAAAAAAAOICAAAAAAAA4wIAAAAAAAAAAAAA4wIAAAAAAADkAgAAAAAAAAAAAADkAgAAAAAAAOUCAAAAAAAAAAAAAOUCAAAAAAAA5gIAAAAAAAAAAAAA5gIAAAAAAADnAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3gIAAAAAAADmAgAAAAAAAAAAAAAAAPC/" + ], + [ + 355, + 1, + "right_delete", + null, + "AQAAAP4CAAAAAAAA/gIAAAAAAAA6AAAAcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbCgpKQ", + "AQAAAAAAAAABAAAA/gIAAAAAAAA4AwAAAAAAAP///////+9/" + ], + [ + 357, + 1, + "left_delete", + null, + "AQAAAPkCAAAAAAAA+QIAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAA+gIAAAAAAAD6AgAAAAAAAAAAAAAAAAAA" + ], + [ + 360, + 1, + "trim_trailing_white_space", + null, + "AQAAAPkCAAAAAAAA+QIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAA/gIAAAAAAAD+AgAAAAAAAAAAAAAAgH9A" + ], + [ + 363, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAACQDAAAAAAAAJQMAAAAAAAAAAAAAJQMAAAAAAAApAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJAMAAAAAAAAkAwAAAAAAAP///////+9/" + ], + [ + 364, + 1, + "insert", + { + "characters": "return" + }, + "BgAAACkDAAAAAAAAKgMAAAAAAAAAAAAAKgMAAAAAAAArAwAAAAAAAAAAAAArAwAAAAAAACwDAAAAAAAAAAAAACwDAAAAAAAALQMAAAAAAAAAAAAALQMAAAAAAAAuAwAAAAAAAAAAAAAuAwAAAAAAAC8DAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKQMAAAAAAAApAwAAAAAAAAAAAAAAAPC/" + ], + [ + 365, + 1, + "insert", + { + "characters": " " + }, + "AQAAAC8DAAAAAAAAMAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALwMAAAAAAAAvAwAAAAAAAAAAAAAAAPC/" + ], + [ + 372, + 1, + "paste", + null, + "AQAAADADAAAAAAAAXQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMAMAAAAAAAAwAwAAAAAAAP///////+9/" + ], + [ + 377, + 1, + "insert", + { + "characters": "url" + }, + "BAAAAEIDAAAAAAAAQwMAAAAAAAAAAAAAQwMAAAAAAABDAwAAAAAAAAkAAABwdWJsaXNoZWRDAwAAAAAAAEQDAAAAAAAAAAAAAEQDAAAAAAAARQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQgMAAAAAAABLAwAAAAAAAAAAAAAAAPC/" + ], + [ + 380, + 1, + "insert", + { + "characters": "url" + }, + "BAAAAEkDAAAAAAAASgMAAAAAAAAAAAAASgMAAAAAAABKAwAAAAAAAAQAAABOb25lSgMAAAAAAABLAwAAAAAAAAAAAABLAwAAAAAAAEwDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASQMAAAAAAABNAwAAAAAAAAAAAAAAAPC/" + ], + [ + 381, + 1, + "insert", + { + "characters": " &&" + }, + "AwAAAEwDAAAAAAAATQMAAAAAAAAAAAAATQMAAAAAAABOAwAAAAAAAAAAAABOAwAAAAAAAE8DAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATAMAAAAAAABMAwAAAAAAAAAAAAAAAPC/" + ], + [ + 382, + 1, + "insert", + { + "characters": " " + }, + "AQAAAE8DAAAAAAAAUAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" + ], + [ + 383, + 1, + "left_delete", + null, + "AQAAAE8DAAAAAAAATwMAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAUAMAAAAAAABQAwAAAAAAAAAAAAAAAPC/" + ], + [ + 384, + 1, + "insert", + { + "characters": " " + }, + "AQAAAE8DAAAAAAAAUAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" + ], + [ + 385, + 2, + "left_delete", + null, + "AgAAAE8DAAAAAAAATwMAAAAAAAABAAAAIE4DAAAAAAAATgMAAAAAAAABAAAAJg", + "AQAAAAAAAAABAAAAUAMAAAAAAABQAwAAAAAAAAAAAAAAAPC/" + ], + [ + 386, + 1, + "insert", + { + "characters": " " + }, + "AQAAAE4DAAAAAAAATwMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgMAAAAAAABOAwAAAAAAAAAAAAAAAPC/" + ], + [ + 394, + 1, + "paste", + null, + "AQAAAE8DAAAAAAAAXAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" + ], + [ + 395, + 1, + "insert", + { + "characters": "published" + }, + "CQAAAFwDAAAAAAAAXQMAAAAAAAAAAAAAXQMAAAAAAABeAwAAAAAAAAAAAABeAwAAAAAAAF8DAAAAAAAAAAAAAF8DAAAAAAAAYAMAAAAAAAAAAAAAYAMAAAAAAABhAwAAAAAAAAAAAABhAwAAAAAAAGIDAAAAAAAAAAAAAGIDAAAAAAAAYwMAAAAAAAAAAAAAYwMAAAAAAABkAwAAAAAAAAAAAABkAwAAAAAAAGUDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXAMAAAAAAABcAwAAAAAAAAAAAAAAAPC/" + ], + [ + 396, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAAGUDAAAAAAAAZgMAAAAAAAAAAAAAZgMAAAAAAABnAwAAAAAAAAAAAABnAwAAAAAAAGgDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZQMAAAAAAABlAwAAAAAAAAAAAAAAAPC/" + ], + [ + 397, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAGgDAAAAAAAAaQMAAAAAAAAAAAAAaQMAAAAAAABqAwAAAAAAAAAAAABqAwAAAAAAAGsDAAAAAAAAAAAAAGsDAAAAAAAAbAMAAAAAAAAAAAAAbAMAAAAAAABtAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaAMAAAAAAABoAwAAAAAAAAAAAAAAAPC/" + ], + [ + 401, + 1, + "insert", + { + "characters": "!" + }, + "AgAAAGYDAAAAAAAAZwMAAAAAAAAAAAAAZwMAAAAAAABnAwAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAZgMAAAAAAABnAwAAAAAAAAAAAAAAAPC/" + ], + [ + 406, + 1, + "insert", + { + "characters": "(" + }, + "AQAAADUDAAAAAAAANgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQMAAAAAAAA1AwAAAAAAAAAAAAAAAPC/" + ], + [ + 409, + 1, + "insert", + { + "characters": ")" + }, + "AQAAAE0DAAAAAAAATgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQMAAAAAAABNAwAAAAAAAAAAAAAAAPC/" + ], + [ + 412, + 1, + "insert", + { + "characters": "(" + }, + "AQAAAFEDAAAAAAAAUgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQMAAAAAAABRAwAAAAAAAAAAAAAAAPC/" + ], + [ + 416, + 1, + "insert", + { + "characters": ")" + }, + "AQAAAHEDAAAAAAAAcgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcQMAAAAAAABxAwAAAAAAAAAAAAAAAPC/" + ], + [ + 420, + 1, + "insert", + { + "characters": ".sot" + }, + "BAAAAHsDAAAAAAAAfAMAAAAAAAAAAAAAfAMAAAAAAAB9AwAAAAAAAAAAAAB9AwAAAAAAAH4DAAAAAAAAAAAAAH4DAAAAAAAAfwMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAewMAAAAAAAB7AwAAAAAAAAAAAAAAAPC/" + ], + [ + 421, + 1, + "left_delete", + null, + "AQAAAH4DAAAAAAAAfgMAAAAAAAABAAAAdA", + "AQAAAAAAAAABAAAAfwMAAAAAAAB/AwAAAAAAAAAAAAAAAPC/" + ], + [ + 422, + 1, + "insert", + { + "characters": "rt-" + }, + "AwAAAH4DAAAAAAAAfwMAAAAAAAAAAAAAfwMAAAAAAACAAwAAAAAAAAAAAACAAwAAAAAAAIEDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgMAAAAAAAB+AwAAAAAAAAAAAAAAAPC/" + ], + [ + 423, + 1, + "left_delete", + null, + "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", + "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" + ], + [ + 424, + 1, + "insert", + { + "characters": "-" + }, + "AQAAAIADAAAAAAAAgQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" + ], + [ + 425, + 1, + "left_delete", + null, + "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", + "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" + ], + [ + 426, + 1, + "insert", + { + "characters": "-" + }, + "AQAAAIADAAAAAAAAgQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" + ], + [ + 427, + 1, + "left_delete", + null, + "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", + "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" + ], + [ + 428, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAIADAAAAAAAAggMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" + ], + [ + 429, + 1, + "insert", + { + "characters": "lamba" + }, + "BQAAAIEDAAAAAAAAggMAAAAAAAAAAAAAggMAAAAAAACDAwAAAAAAAAAAAACDAwAAAAAAAIQDAAAAAAAAAAAAAIQDAAAAAAAAhQMAAAAAAAAAAAAAhQMAAAAAAACGAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" + ], + [ + 430, + 1, + "left_delete", + null, + "AQAAAIUDAAAAAAAAhQMAAAAAAAABAAAAYQ", + "AQAAAAAAAAABAAAAhgMAAAAAAACGAwAAAAAAAAAAAAAAAPC/" + ], + [ + 431, + 1, + "insert", + { + "characters": "da:" + }, + "AwAAAIUDAAAAAAAAhgMAAAAAAAAAAAAAhgMAAAAAAACHAwAAAAAAAAAAAACHAwAAAAAAAIgDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQMAAAAAAACFAwAAAAAAAAAAAAAAAPC/" + ], + [ + 432, + 1, + "insert", + { + "characters": " sort" + }, + "BQAAAIgDAAAAAAAAiQMAAAAAAAAAAAAAiQMAAAAAAACKAwAAAAAAAAAAAACKAwAAAAAAAIsDAAAAAAAAAAAAAIsDAAAAAAAAjAMAAAAAAAAAAAAAjAMAAAAAAACNAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiAMAAAAAAACIAwAAAAAAAAAAAAAAAPC/" + ], + [ + 433, + 1, + "insert", + { + "characters": " " + }, + "AQAAAI0DAAAAAAAAjgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" + ], + [ + 437, + 1, + "insert", + { + "characters": "row:" + }, + "BQAAAIkDAAAAAAAAigMAAAAAAAAAAAAAigMAAAAAAACKAwAAAAAAAAQAAABzb3J0igMAAAAAAACLAwAAAAAAAAAAAACLAwAAAAAAAIwDAAAAAAAAAAAAAIwDAAAAAAAAjQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" + ], + [ + 438, + 1, + "insert", + { + "characters": " " + }, + "AQAAAI0DAAAAAAAAjgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" + ], + [ + 439, + 1, + "insert", + { + "characters": "row" + }, + "AwAAAI4DAAAAAAAAjwMAAAAAAAAAAAAAjwMAAAAAAACQAwAAAAAAAAAAAACQAwAAAAAAAJEDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjgMAAAAAAACOAwAAAAAAAAAAAAAAAPC/" + ], + [ + 440, + 1, + "insert", + { + "characters": ".publ" + }, + "BQAAAJEDAAAAAAAAkgMAAAAAAAAAAAAAkgMAAAAAAACTAwAAAAAAAAAAAACTAwAAAAAAAJQDAAAAAAAAAAAAAJQDAAAAAAAAlQMAAAAAAAAAAAAAlQMAAAAAAACWAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkQMAAAAAAACRAwAAAAAAAAAAAAAAAPC/" + ], + [ + 441, + 1, + "insert", + { + "characters": "ish" + }, + "AwAAAJYDAAAAAAAAlwMAAAAAAAAAAAAAlwMAAAAAAACYAwAAAAAAAAAAAACYAwAAAAAAAJkDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlgMAAAAAAACWAwAAAAAAAAAAAAAAAPC/" + ], + [ + 442, + 1, + "insert", + { + "characters": "ed" + }, + "AgAAAJkDAAAAAAAAmgMAAAAAAAAAAAAAmgMAAAAAAACbAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAmQMAAAAAAACZAwAAAAAAAAAAAAAAAPC/" + ], + [ + 443, + 1, + "right_delete", + null, + "AQAAAJsDAAAAAAAAmwMAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAmwMAAAAAAACbAwAAAAAAAAAAAAAAAPC/" + ], + [ + 448, + 1, + "right_delete", + null, + "AQAAAJ0DAAAAAAAAnQMAAAAAAACjAAAAICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5vcmRlcl9ieSgrQ29tbWVudC5wdWJsaXNoZWQpCiAgICApCg", + "AQAAAAAAAAABAAAAnQMAAAAAAABABAAAAAAAAAAAAAAAAAAA" + ], + [ + 451, + 1, + "left_delete", + null, + "AQAAAIcDAAAAAAAAhwMAAAAAAAABAAAAOg", + "AQAAAAAAAAABAAAAiAMAAAAAAACIAwAAAAAAAAAAAAAAAPC/" + ], + [ + 461, + 1, + "right_delete", + null, + "AQAAAB4FAAAAAAAAHgUAAAAAAAAeAAAALnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIp", + "AQAAAAAAAAABAAAAHgUAAAAAAAA8BQAAAAAAAP///////+9/" + ], + [ + 470, + 1, + "insert", + { + "characters": "\n\n" + }, + "BQAAAMADAAAAAAAAwQMAAAAAAAAAAAAAwQMAAAAAAADFAwAAAAAAAAAAAADFAwAAAAAAAMYDAAAAAAAAAAAAAMYDAAAAAAAAygMAAAAAAAAAAAAAwQMAAAAAAADBAwAAAAAAAAQAAAAgICAg", + "AQAAAAAAAAABAAAAwAMAAAAAAADAAwAAAAAAAAAAAAAAAPC/" + ], + [ + 472, + 1, + "reindent", + null, + "AQAAAMEDAAAAAAAAxQMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQMAAAAAAADBAwAAAAAAAAAAAAAAAEJA" + ], + [ + 473, + 1, + "insert", + { + "characters": "return" + }, + "BwAAAMUDAAAAAAAAxgMAAAAAAAAAAAAAxwMAAAAAAADHAwAAAAAAAAQAAAAgICAgxgMAAAAAAADHAwAAAAAAAAAAAADHAwAAAAAAAMgDAAAAAAAAAAAAAMgDAAAAAAAAyQMAAAAAAAAAAAAAyQMAAAAAAADKAwAAAAAAAAAAAADKAwAAAAAAAMsDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQMAAAAAAADFAwAAAAAAAAAAAAAAAPC/" + ], + [ + 474, + 1, + "insert", + { + "characters": " " + }, + "AQAAAMsDAAAAAAAAzAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAywMAAAAAAADLAwAAAAAAAAAAAAAAAPC/" + ], + [ + 480, + 1, + "paste", + null, + "AQAAAMwDAAAAAAAAFwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzAMAAAAAAADMAwAAAAAAAAAAAAAAAPC/" + ], + [ + 485, + 1, + "insert", + { + "characters": "==" + }, + "AwAAAAUEAAAAAAAABgQAAAAAAAAAAAAABgQAAAAAAAAGBAAAAAAAAAIAAAAhPQYEAAAAAAAABwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABQQAAAAAAAAHBAAAAAAAAAAAAAAAAPC/" + ], + [ + 487, + 1, + "insert", + { + "characters": " " + }, + "AQAAABcEAAAAAAAAGAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFwQAAAAAAAAXBAAAAAAAAP///////+9/" + ], + [ + 488, + 1, + "left_delete", + null, + "AQAAABcEAAAAAAAAFwQAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAGAQAAAAAAAAYBAAAAAAAAAAAAAAAAPC/" + ], + [ + 489, + 1, + "insert", + { + "characters": ".count" + }, + "BgAAABcEAAAAAAAAGAQAAAAAAAAAAAAAGAQAAAAAAAAZBAAAAAAAAAAAAAAZBAAAAAAAABoEAAAAAAAAAAAAABoEAAAAAAAAGwQAAAAAAAAAAAAAGwQAAAAAAAAcBAAAAAAAAAAAAAAcBAAAAAAAAB0EAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFwQAAAAAAAAXBAAAAAAAAAAAAAAAAPC/" + ], + [ + 490, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAB0EAAAAAAAAHwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHQQAAAAAAAAdBAAAAAAAAAAAAAAAAPC/" + ], + [ + 492, + 1, + "insert", + { + "characters": " if" + }, + "AwAAAB8EAAAAAAAAIAQAAAAAAAAAAAAAIAQAAAAAAAAhBAAAAAAAAAAAAAAhBAAAAAAAACIEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHwQAAAAAAAAfBAAAAAAAAAAAAAAAAPC/" + ], + [ + 493, + 1, + "insert", + { + "characters": " u" + }, + "AgAAACIEAAAAAAAAIwQAAAAAAAAAAAAAIwQAAAAAAAAkBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIgQAAAAAAAAiBAAAAAAAAAAAAAAAAPC/" + ], + [ + 494, + 1, + "insert", + { + "characters": "rl" + }, + "AgAAACQEAAAAAAAAJQQAAAAAAAAAAAAAJQQAAAAAAAAmBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJAQAAAAAAAAkBAAAAAAAAAAAAAAAAPC/" + ], + [ + 495, + 1, + "insert", + { + "characters": " " + }, + "AQAAACYEAAAAAAAAJwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 496, + 1, + "insert", + { + "characters": "\nelse" + }, + "BgAAACcEAAAAAAAAKAQAAAAAAAAAAAAAKAQAAAAAAAAsBAAAAAAAAAAAAAAsBAAAAAAAAC0EAAAAAAAAAAAAAC0EAAAAAAAALgQAAAAAAAAAAAAALgQAAAAAAAAvBAAAAAAAAAAAAAAvBAAAAAAAADAEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJwQAAAAAAAAnBAAAAAAAAAAAAAAAAPC/" + ], + [ + 497, + 1, + "insert", + { + "characters": " " + }, + "AQAAADAEAAAAAAAAMQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAAPC/" + ], + [ + 504, + 1, + "paste", + null, + "AQAAADEEAAAAAAAAjAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMQQAAAAAAAAxBAAAAAAAAAAAAAAAAPC/" + ], + [ + 511, + 1, + "right_delete", + null, + "AQAAADYEAAAAAAAANgQAAAAAAAAdAAAAKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmICg", + "AQAAAAAAAAABAAAANgQAAAAAAABTBAAAAAAAAAAAAAAAAPC/" + ], + [ + 515, + 1, + "left_delete", + null, + "AQAAAFQEAAAAAAAAVAQAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAAVQQAAAAAAABVBAAAAAAAAAAAAAAAAPC/" + ], + [ + 520, + 1, + "right_delete", + null, + "AQAAAGcEAAAAAAAAZwQAAAAAAAAGAAAAaWYgdXJs", + "AQAAAAAAAAABAAAAZwQAAAAAAABtBAAAAAAAAAAAAAAAAPC/" + ], + [ + 527, + 1, + "insert", + { + "characters": "is" + }, + "AwAAAAUEAAAAAAAABgQAAAAAAAAAAAAABgQAAAAAAAAGBAAAAAAAAAIAAAA9PQYEAAAAAAAABwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABQQAAAAAAAAHBAAAAAAAAAAAAAAAAPC/" + ], + [ + 532, + 1, + "insert", + { + "characters": "is" + }, + "AwAAAE0EAAAAAAAATgQAAAAAAAAAAAAATgQAAAAAAABOBAAAAAAAAAIAAAA9PU4EAAAAAAAATwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQQAAAAAAABPBAAAAAAAAAAAAAAAAPC/" + ], + [ + 537, + 1, + "right_delete", + null, + "AQAAAGoEAAAAAAAAagQAAAAAAADyAAAAICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBDb21tZW50LnNlbGVjdChDb21tZW50KS53aGVyZShDb21tZW50LnB1Ymxpc2hlZC5pc19udWxsKEZhbHNlKSkuY291bnQoKQogICAgKQo", + "AQAAAAAAAAABAAAAagQAAAAAAABcBQAAAAAAAAAAAAAAAAAA" + ], + [ + 538, + 1, + "left_delete", + null, + "AQAAAGkEAAAAAAAAaQQAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAagQAAAAAAABqBAAAAAAAAAAAAAAAAPC/" + ], + [ + 540, + 1, + "trim_trailing_white_space", + null, + "AgAAAGYEAAAAAAAAZgQAAAAAAAACAAAAICAmBAAAAAAAACYEAAAAAAAAAQAAACA", + "AQAAAAAAAAABAAAAaQQAAAAAAABpBAAAAAAAAAAAAAAAAPC/" + ], + [ + 546, + 1, + "insert", + { + "characters": "\t" + }, + "AQAAACsEAAAAAAAALwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKwQAAAAAAAArBAAAAAAAAAAAAAAAAPC/" + ], + [ + 554, + 1, + "right_delete", + null, + "AQAAACYEAAAAAAAAJgQAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 555, + 1, + "insert", + { + "characters": " " + }, + "AQAAACYEAAAAAAAAJwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 558, + 1, + "right_delete", + null, + "AQAAACcEAAAAAAAAJwQAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAJwQAAAAAAAAvBAAAAAAAAAAAAAAAAPC/" + ], + [ + 566, + 1, + "insert", + { + "characters": "is" + }, + "AwAAAOgCAAAAAAAA6QIAAAAAAAAAAAAA6QIAAAAAAADpAgAAAAAAAAIAAAA9PekCAAAAAAAA6gIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6AIAAAAAAADqAgAAAAAAAAAAAAAAAPC/" + ], + [ + 571, + 1, + "insert", + { + "characters": "is" + }, + "AwAAAIoCAAAAAAAAiwIAAAAAAAAAAAAAiwIAAAAAAACLAgAAAAAAAAIAAAA9PYsCAAAAAAAAjAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjAIAAAAAAACKAgAAAAAAAAAAAAAAAPC/" + ], + [ + 578, + 1, + "insert", + { + "characters": "is" + }, + "AgAAAGkDAAAAAAAAagMAAAAAAAAAAAAAagMAAAAAAABrAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaQMAAAAAAABpAwAAAAAAAAAAAAAAAPC/" + ], + [ + 579, + 1, + "insert", + { + "characters": " not" + }, + "BAAAAGsDAAAAAAAAbAMAAAAAAAAAAAAAbAMAAAAAAABtAwAAAAAAAAAAAABtAwAAAAAAAG4DAAAAAAAAAAAAAG4DAAAAAAAAbwMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAawMAAAAAAABrAwAAAAAAAAAAAAAAAPC/" + ], + [ + 580, + 1, + "insert", + { + "characters": " " + }, + "AQAAAG8DAAAAAAAAcAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbwMAAAAAAABvAwAAAAAAAAAAAAAAAPC/" + ], + [ + 582, + 2, + "right_delete", + null, + "AgAAAHADAAAAAAAAcAMAAAAAAAACAAAAIT1wAwAAAAAAAHADAAAAAAAAAQAAACA", + "AQAAAAAAAAABAAAAcAMAAAAAAAByAwAAAAAAAAAAAAAAAPC/" + ], + [ + 589, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAANEEAAAAAAAA0gQAAAAAAAAAAAAA0gQAAAAAAADWBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0QQAAAAAAADRBAAAAAAAAAAAAAAAAPC/" + ], + [ + 595, + 1, + "paste", + null, + "AQAAANYEAAAAAAAA+gQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1gQAAAAAAADWBAAAAAAAAAAAAAAAAPC/" + ], + [ + 600, + 1, + "right_delete", + null, + "AQAAAOoEAAAAAAAA6gQAAAAAAAAPAAAAYXNkaWN0KGNvbW1lbnQp", + "AQAAAAAAAAABAAAA6gQAAAAAAAD5BAAAAAAAAAAAAAAAAPC/" + ], + [ + 601, + 1, + "insert", + { + "characters": "\n" + }, + "BAAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA6wQAAAAAAADvBAAAAAAAAAAAAADrBAAAAAAAAOsEAAAAAAAABAAAACAgICDrBAAAAAAAAPMEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAAAAAAAAAPC/" + ], + [ + 604, + 1, + "insert", + { + "characters": "\n" + }, + "BAAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA6wQAAAAAAADvBAAAAAAAAAAAAADrBAAAAAAAAOsEAAAAAAAABAAAACAgICDrBAAAAAAAAPMEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAAAAAAAAAPC/" + ], + [ + 607, + 1, + "cut", + null, + "AQAAABUFAAAAAAAAFQUAAAAAAADcAAAAICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9Y3JlYXRlZCwKICAgICAgICBub3RpZmllZD1Ob25lLAogICAgICAgIHB1Ymxpc2hlZD1Ob25lLA", + "AQAAAAAAAAABAAAA8QUAAAAAAAAVBQAAAAAAAAAAAAAAAPC/" + ], + [ + 610, + 1, + "paste", + null, + "AQAAAOwEAAAAAAAAyAUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7AQAAAAAAADsBAAAAAAAAAAAAAAAAPC/" + ], + [ + 613, + 1, + "right_delete", + null, + "AQAAAPMEAAAAAAAA8wQAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAA8wQAAAAAAADzBAAAAAAAAAAAAAAAAPC/" + ], + [ + 618, + 1, + "cut", + null, + "AQAAAMMEAAAAAAAAwwQAAAAAAAAOAAAAZGF0ZXRpbWUubm93KCk", + "AQAAAAAAAAABAAAAwwQAAAAAAADRBAAAAAAAAAAAAAAAAPC/" + ], + [ + 623, + 1, + "paste", + null, + "AgAAAIIFAAAAAAAAkAUAAAAAAAAAAAAAkAUAAAAAAACQBQAAAAAAAAcAAABjcmVhdGVk", + "AQAAAAAAAAABAAAAggUAAAAAAACJBQAAAAAAAAAAAAAAAPC/" + ], + [ + 626, + 2, + "left_delete", + null, + "AgAAANIFAAAAAAAA0gUAAAAAAAAwAAAAICAgIGNvbW1lbnQgPSBDb21tZW50KAoKICAgICkKICAgIGNvbW1lbnQuc2F2ZSgp0QUAAAAAAADRBQAAAAAAAAEAAAAK", + "AQAAAAAAAAABAAAAAgYAAAAAAADSBQAAAAAAAAAAAAAAAPC/" + ], + [ + 631, + 1, + "right_delete", + null, + "AQAAALkEAAAAAAAAuQQAAAAAAAAKAAAAY3JlYXRlZCA9IA", + "AQAAAAAAAAABAAAAwwQAAAAAAAC5BAAAAAAAAAAAAAAAAEJA" + ], + [ + 633, + 1, + "left_delete", + null, + "AQAAALQEAAAAAAAAtAQAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAtQQAAAAAAAC1BAAAAAAAAAAAAAAAAAAA" + ], + [ + 635, + 1, + "trim_trailing_white_space", + null, + "AgAAALUFAAAAAAAAtQUAAAAAAAAHAAAAICAgICAgILQEAAAAAAAAtAQAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAtAQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" + ], + [ + 651, + 1, + "insert", + { + "characters": "comment" + }, + "BwAAALkEAAAAAAAAugQAAAAAAAAAAAAAugQAAAAAAAC7BAAAAAAAAAAAAAC7BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAL8EAAAAAAAAwAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuQQAAAAAAAC5BAAAAAAAAAAAAAAAAPC/" + ], + [ + 652, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAMAEAAAAAAAAwQQAAAAAAAAAAAAAwQQAAAAAAADCBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwAQAAAAAAADABAAAAAAAAAAAAAAAAPC/" + ], + [ + 653, + 1, + "insert", + { + "characters": " " + }, + "AQAAAMIEAAAAAAAAwwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwgQAAAAAAADCBAAAAAAAAAAAAAAAAPC/" + ], + [ + 660, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADZBQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKCmRlZiBmaW5kX2NvbW1lbnRfYnlfaWQoY29tbWVudF9pZCk6CiAgICByZXR1cm4gZGIoKS5jb21tZW50KGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5ub3RpZmllZCA9IGRhdGV0aW1lLm5vdygpCiAgICBkYigpLmNvbW1lbnQuaW5zZXJ0KGFzZGljdChjb21tZW50KSkKCgpkZWYgcHVibGlzaF9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5wdWJsaXNoZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIGRlbGV0ZV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgZGIoKShkYigpLmNvbW1lbnQuaWQgPT0gY29tbWVudC5pZCkuZGVsZXRlKCkKCgpkZWYgZmluZF9ub3Rfbm90aWZpZWRfY29tbWVudHMoKToKICAgIHJldHVybiBkYigpKGRiKCkuY29tbWVudC5ub3RpZmllZCBpcyBOb25lKS5zZWxlY3QoKQoKCmRlZiBmaW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKToKICAgIHJldHVybiBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgaXMgTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9wdWJsaXNoZWRfY29tbWVudHNfYnlfdXJsKHVybCk6CiAgICByZXR1cm4gZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgaXMgbm90IE5vbmUpKS5zZWxlY3QoKS5zb3J0KGxhbWJkYSByb3c6IHJvdy5wdWJsaXNoZWQpCgoKZGVmIGNvdW50X3B1Ymxpc2hlZF9jb21tZW50cyh1cmwpOgogICAgcmV0dXJuIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkIGlzIE5vbmUpKS5zZWxlY3QoKS5jb3VudCgpIGlmIHVybCBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCBpcyBOb25lKS5zZWxlY3QoKS5jb3VudCgpCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY29tbWVudCA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICAgICAgKQogICAgcmV0dXJuIGNvbW1lbnQKAAAAAAAAAAAqBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQUAAAAAAADFBQAAAAAAAAAAAAAAAPC/" + ], + [ + 691, + 1, + "insert", + { + "characters": "==" + }, + "AgAAAIsCAAAAAAAAjAIAAAAAAAAAAAAAjAIAAAAAAACNAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + ], + [ + 692, + 2, + "right_delete", + null, + "AgAAAI0CAAAAAAAAjQIAAAAAAAABAAAAaY0CAAAAAAAAjQIAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAAjQIAAAAAAACNAgAAAAAAAAAAAAAAAPC/" + ], + [ + 696, + 1, + "insert", + { + "characters": "==" + }, + "AgAAAOkCAAAAAAAA6gIAAAAAAAAAAAAA6gIAAAAAAADrAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6QIAAAAAAADpAgAAAAAAAAAAAAAAAPC/" + ], + [ + 697, + 2, + "right_delete", + null, + "AgAAAOsCAAAAAAAA6wIAAAAAAAABAAAAaesCAAAAAAAA6wIAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAA6wIAAAAAAADrAgAAAAAAAAAAAAAAAPC/" + ], + [ + 712, + 1, + "insert", + { + "characters": "!=" + }, + "AwAAAHQDAAAAAAAAdQMAAAAAAAAAAAAAdQMAAAAAAAB1AwAAAAAAAAYAAABpcyBub3R1AwAAAAAAAHYDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdAMAAAAAAAB6AwAAAAAAAAAAAAAAAPC/" + ], + [ + 717, + 1, + "insert", + { + "characters": "==" + }, + "AwAAADIEAAAAAAAAMwQAAAAAAAAAAAAAMwQAAAAAAAAzBAAAAAAAAAIAAABpczMEAAAAAAAANAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMgQAAAAAAAA0BAAAAAAAAAAAAAAAAPC/" + ], + [ + 725, + 1, + "insert", + { + "characters": "==" + }, + "AwAAAJcEAAAAAAAAmAQAAAAAAAAAAAAAmAQAAAAAAACYBAAAAAAAAAIAAABpc5gEAAAAAAAAmQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlwQAAAAAAACZBAAAAAAAAAAAAAAAAPC/" + ], + [ + 741, + 2, + "right_delete", + null, + "AgAAAKAEAAAAAAAAoAQAAAAAAAAIAAAAc2VsZWN0KCmgBAAAAAAAAKAEAAAAAAAAAQAAAC4", + "AQAAAAAAAAABAAAAoAQAAAAAAACoBAAAAAAAAAAAAAAAAPC/" + ], + [ + 744, + 1, + "right_delete", + null, + "AQAAAEQEAAAAAAAARAQAAAAAAAAJAAAALnNlbGVjdCgp", + "AQAAAAAAAAABAAAATQQAAAAAAABEBAAAAAAAAAAAAAAAAFJA" + ], + [ + 746, + 1, + "left_delete", + null, + "AQAAADsEAAAAAAAAOwQAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAPAQAAAAAAAA8BAAAAAAAAAAAAAAAAAAA" + ], + [ + 748, + 1, + "trim_trailing_white_space", + null, + "AQAAADsEAAAAAAAAOwQAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAOwQAAAAAAAA7BAAAAAAAAAAAAAAAAPC/" + ], + [ + 749, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAAALBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIHB1Ymxpc2hfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5pbnNlcnQoYXNkaWN0KGNvbW1lbnQpKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KCkKICAgICAgICAuc29ydChsYW1iZGEgcm93OiByb3cucHVibGlzaGVkKQogICAgKQoKCmRlZiBjb3VudF9wdWJsaXNoZWRfY29tbWVudHModXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkpCiAgICAgICAgLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY29tbWVudCA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICByZXR1cm4gY29tbWVudAoAAAAAAAAAAAIGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOwQAAAAAAAA7BAAAAAAAAAAAAAAAAPC/" + ], + [ + 790, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAO4FAAAAAAAA7wUAAAAAAAAAAAAA7wUAAAAAAADzBQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7gUAAAAAAADuBQAAAAAAAAAAAAAAAPC/" + ], + [ + 796, + 1, + "insert", + { + "characters": "Comment" + }, + "CQAAAP8FAAAAAAAAAAYAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAcAAABjb21tZW507wUAAAAAAADvBQAAAAAAAAQAAAAgICAg/AUAAAAAAAD9BQAAAAAAAAAAAAD9BQAAAAAAAP4FAAAAAAAAAAAAAP4FAAAAAAAA/wUAAAAAAAAAAAAA/wUAAAAAAAAABgAAAAAAAAAAAAAABgAAAAAAAAEGAAAAAAAAAAAAAAEGAAAAAAAAAgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wUAAAAAAAAGBgAAAAAAAAAAAAAAAPC/" + ], + [ + 797, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAAIGAAAAAAAABAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAgYAAAAAAAACBgAAAAAAAAAAAAAAAPC/" + ], + [ + 798, + 1, + "insert", + { + "characters": "id=com" + }, + "BgAAAAMGAAAAAAAABAYAAAAAAAAAAAAABAYAAAAAAAAFBgAAAAAAAAAAAAAFBgAAAAAAAAYGAAAAAAAAAAAAAAYGAAAAAAAABwYAAAAAAAAAAAAABwYAAAAAAAAIBgAAAAAAAAAAAAAIBgAAAAAAAAkGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAwYAAAAAAAADBgAAAAAAAAAAAAAAAPC/" + ], + [ + 799, + 1, + "insert", + { + "characters": "ment." + }, + "BQAAAAkGAAAAAAAACgYAAAAAAAAAAAAACgYAAAAAAAALBgAAAAAAAAAAAAALBgAAAAAAAAwGAAAAAAAAAAAAAAwGAAAAAAAADQYAAAAAAAAAAAAADQYAAAAAAAAOBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACQYAAAAAAAAJBgAAAAAAAAAAAAAAAPC/" + ], + [ + 800, + 1, + "insert", + { + "characters": "id" + }, + "AgAAAA4GAAAAAAAADwYAAAAAAAAAAAAADwYAAAAAAAAQBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADgYAAAAAAAAOBgAAAAAAAAAAAAAAAPC/" + ], + [ + 801, + 1, + "insert", + { + "characters": "," + }, + "AQAAABAGAAAAAAAAEQYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEAYAAAAAAAAQBgAAAAAAAAAAAAAAAPC/" + ], + [ + 802, + 1, + "insert", + { + "characters": " " + }, + "AQAAABEGAAAAAAAAEgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEQYAAAAAAAARBgAAAAAAAAAAAAAAAPC/" + ], + [ + 803, + 1, + "insert", + { + "characters": "url=" + }, + "BAAAABIGAAAAAAAAEwYAAAAAAAAAAAAAEwYAAAAAAAAUBgAAAAAAAAAAAAAUBgAAAAAAABUGAAAAAAAAAAAAABUGAAAAAAAAFgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgYAAAAAAAASBgAAAAAAAAAAAAAAAPC/" + ], + [ + 804, + 1, + "insert", + { + "characters": " " + }, + "AQAAABYGAAAAAAAAFwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" + ], + [ + 805, + 1, + "left_delete", + null, + "AQAAABYGAAAAAAAAFgYAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAFwYAAAAAAAAXBgAAAAAAAAAAAAAAAPC/" + ], + [ + 806, + 1, + "insert", + { + "characters": "comment" + }, + "BwAAABYGAAAAAAAAFwYAAAAAAAAAAAAAFwYAAAAAAAAYBgAAAAAAAAAAAAAYBgAAAAAAABkGAAAAAAAAAAAAABkGAAAAAAAAGgYAAAAAAAAAAAAAGgYAAAAAAAAbBgAAAAAAAAAAAAAbBgAAAAAAABwGAAAAAAAAAAAAABwGAAAAAAAAHQYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" + ], + [ + 812, + 1, + "insert", + { + "characters": "row" + }, + "BAAAAOYEAAAAAAAA5wQAAAAAAAAAAAAA5wQAAAAAAADnBAAAAAAAAAcAAABjb21tZW505wQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOkEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5gQAAAAAAADtBAAAAAAAAAAAAAAAAPC/" + ], + [ + 817, + 1, + "insert", + { + "characters": "row" + }, + "BAAAAAIGAAAAAAAAAwYAAAAAAAAAAAAAAwYAAAAAAAADBgAAAAAAAAcAAABjb21tZW50AwYAAAAAAAAEBgAAAAAAAAAAAAAEBgAAAAAAAAUGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAgYAAAAAAAAJBgAAAAAAAAAAAAAAAPC/" + ], + [ + 822, + 1, + "insert", + { + "characters": "row.url" + }, + "CAAAAA4GAAAAAAAADwYAAAAAAAAAAAAADwYAAAAAAAAPBgAAAAAAAAcAAABjb21tZW50DwYAAAAAAAAQBgAAAAAAAAAAAAAQBgAAAAAAABEGAAAAAAAAAAAAABEGAAAAAAAAEgYAAAAAAAAAAAAAEgYAAAAAAAATBgAAAAAAAAAAAAATBgAAAAAAABQGAAAAAAAAAAAAABQGAAAAAAAAFQYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADgYAAAAAAAAVBgAAAAAAAAAAAAAAAPC/" + ], + [ + 823, + 1, + "insert", + { + "characters": "," + }, + "AQAAABUGAAAAAAAAFgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFQYAAAAAAAAVBgAAAAAAAAAAAAAAAPC/" + ], + [ + 824, + 1, + "insert", + { + "characters": " auth" + }, + "BQAAABYGAAAAAAAAFwYAAAAAAAAAAAAAFwYAAAAAAAAYBgAAAAAAAAAAAAAYBgAAAAAAABkGAAAAAAAAAAAAABkGAAAAAAAAGgYAAAAAAAAAAAAAGgYAAAAAAAAbBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" + ], + [ + 827, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_name\",\"filterText\":\"author_name\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":47},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_name=\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_name=" + }, + "AgAAABcGAAAAAAAAFwYAAAAAAAAEAAAAYXV0aBcGAAAAAAAAIwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGwYAAAAAAAAbBgAAAAAAAAAAAAAAAPC/" + ], + [ + 828, + 1, + "insert", + { + "characters": "row.a" + }, + "BQAAACMGAAAAAAAAJAYAAAAAAAAAAAAAJAYAAAAAAAAlBgAAAAAAAAAAAAAlBgAAAAAAACYGAAAAAAAAAAAAACYGAAAAAAAAJwYAAAAAAAAAAAAAJwYAAAAAAAAoBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIwYAAAAAAAAjBgAAAAAAAAAAAAAAAPC/" + ], + [ + 829, + 1, + "insert_completion", + { + "completion": "author_name", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_name" + }, + "AgAAACcGAAAAAAAAJwYAAAAAAAABAAAAYScGAAAAAAAAMgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKAYAAAAAAAAoBgAAAAAAAAAAAAAAAPC/" + ], + [ + 830, + 1, + "insert", + { + "characters": "," + }, + "AQAAADIGAAAAAAAAMwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" + ], + [ + 831, + 1, + "insert", + { + "characters": " auth" + }, + "BQAAADMGAAAAAAAANAYAAAAAAAAAAAAANAYAAAAAAAA1BgAAAAAAAAAAAAA1BgAAAAAAADYGAAAAAAAAAAAAADYGAAAAAAAANwYAAAAAAAAAAAAANwYAAAAAAAA4BgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMwYAAAAAAAAzBgAAAAAAAAAAAAAAAPC/" + ], + [ + 834, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_site\",\"filterText\":\"author_site\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":76},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_site=\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_site=" + }, + "AgAAADQGAAAAAAAANAYAAAAAAAAEAAAAYXV0aDQGAAAAAAAAQAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOAYAAAAAAAA4BgAAAAAAAAAAAAAAAPC/" + ], + [ + 835, + 1, + "insert", + { + "characters": "=row.au" + }, + "BwAAAEAGAAAAAAAAQQYAAAAAAAAAAAAAQQYAAAAAAABCBgAAAAAAAAAAAABCBgAAAAAAAEMGAAAAAAAAAAAAAEMGAAAAAAAARAYAAAAAAAAAAAAARAYAAAAAAABFBgAAAAAAAAAAAABFBgAAAAAAAEYGAAAAAAAAAAAAAEYGAAAAAAAARwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQAYAAAAAAABABgAAAAAAAAAAAAAAAPC/" + ], + [ + 836, + 1, + "insert_completion", + { + "completion": "author_site", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_site" + }, + "AgAAAEUGAAAAAAAARQYAAAAAAAACAAAAYXVFBgAAAAAAAFAGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARwYAAAAAAABHBgAAAAAAAAAAAAAAAPC/" + ], + [ + 837, + 1, + "insert", + { + "characters": "," + }, + "AQAAAFAGAAAAAAAAUQYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUAYAAAAAAABQBgAAAAAAAAAAAAAAAPC/" + ], + [ + 838, + 1, + "insert", + { + "characters": " " + }, + "AQAAAFEGAAAAAAAAUgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQYAAAAAAABRBgAAAAAAAAAAAAAAAPC/" + ], + [ + 839, + 1, + "insert", + { + "characters": "au" + }, + "AgAAAFIGAAAAAAAAUwYAAAAAAAAAAAAAUwYAAAAAAABUBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgYAAAAAAABSBgAAAAAAAAAAAAAAAPC/" + ], + [ + 842, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_gravatar\",\"filterText\":\"author_gravatar\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":104},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_gravatar=\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_gravatar=" + }, + "AgAAAFIGAAAAAAAAUgYAAAAAAAACAAAAYXVSBgAAAAAAAGIGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAYAAAAAAABUBgAAAAAAAAAAAAAAAPC/" + ], + [ + 843, + 1, + "insert", + { + "characters": "row.a" + }, + "BQAAAGIGAAAAAAAAYwYAAAAAAAAAAAAAYwYAAAAAAABkBgAAAAAAAAAAAABkBgAAAAAAAGUGAAAAAAAAAAAAAGUGAAAAAAAAZgYAAAAAAAAAAAAAZgYAAAAAAABnBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYgYAAAAAAABiBgAAAAAAAAAAAAAAAPC/" + ], + [ + 844, + 1, + "insert_completion", + { + "completion": "author_gravatar", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_gravatar" + }, + "AgAAAGYGAAAAAAAAZgYAAAAAAAABAAAAYWYGAAAAAAAAdQYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZwYAAAAAAABnBgAAAAAAAAAAAAAAAPC/" + ], + [ + 845, + 1, + "insert", + { + "characters": "," + }, + "AQAAAHUGAAAAAAAAdgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdQYAAAAAAAB1BgAAAAAAAAAAAAAAAPC/" + ], + [ + 846, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHYGAAAAAAAAdwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgYAAAAAAAB2BgAAAAAAAAAAAAAAAPC/" + ], + [ + 847, + 1, + "insert", + { + "characters": "contnt" + }, + "BgAAAHcGAAAAAAAAeAYAAAAAAAAAAAAAeAYAAAAAAAB5BgAAAAAAAAAAAAB5BgAAAAAAAHoGAAAAAAAAAAAAAHoGAAAAAAAAewYAAAAAAAAAAAAAewYAAAAAAAB8BgAAAAAAAAAAAAB8BgAAAAAAAH0GAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdwYAAAAAAAB3BgAAAAAAAAAAAAAAAPC/" + ], + [ + 848, + 2, + "left_delete", + null, + "AgAAAHwGAAAAAAAAfAYAAAAAAAABAAAAdHsGAAAAAAAAewYAAAAAAAABAAAAbg", + "AQAAAAAAAAABAAAAfQYAAAAAAAB9BgAAAAAAAAAAAAAAAPC/" + ], + [ + 849, + 1, + "insert", + { + "characters": "ent=" + }, + "BAAAAHsGAAAAAAAAfAYAAAAAAAAAAAAAfAYAAAAAAAB9BgAAAAAAAAAAAAB9BgAAAAAAAH4GAAAAAAAAAAAAAH4GAAAAAAAAfwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAewYAAAAAAAB7BgAAAAAAAAAAAAAAAPC/" + ], + [ + 850, + 8, + "left_delete", + null, + "CAAAAH4GAAAAAAAAfgYAAAAAAAABAAAAPX0GAAAAAAAAfQYAAAAAAAABAAAAdHwGAAAAAAAAfAYAAAAAAAABAAAAbnsGAAAAAAAAewYAAAAAAAABAAAAZXoGAAAAAAAAegYAAAAAAAABAAAAdHkGAAAAAAAAeQYAAAAAAAABAAAAbngGAAAAAAAAeAYAAAAAAAABAAAAb3cGAAAAAAAAdwYAAAAAAAABAAAAYw", + "AQAAAAAAAAABAAAAfwYAAAAAAAB/BgAAAAAAAAAAAAAAAPC/" + ], + [ + 851, + 1, + "insert", + { + "characters": "content=row" + }, + "CwAAAHcGAAAAAAAAeAYAAAAAAAAAAAAAeAYAAAAAAAB5BgAAAAAAAAAAAAB5BgAAAAAAAHoGAAAAAAAAAAAAAHoGAAAAAAAAewYAAAAAAAAAAAAAewYAAAAAAAB8BgAAAAAAAAAAAAB8BgAAAAAAAH0GAAAAAAAAAAAAAH0GAAAAAAAAfgYAAAAAAAAAAAAAfgYAAAAAAAB/BgAAAAAAAAAAAAB/BgAAAAAAAIAGAAAAAAAAAAAAAIAGAAAAAAAAgQYAAAAAAAAAAAAAgQYAAAAAAACCBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdwYAAAAAAAB3BgAAAAAAAAAAAAAAAPC/" + ], + [ + 852, + 1, + "insert", + { + "characters": "." + }, + "AQAAAIIGAAAAAAAAgwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAggYAAAAAAACCBgAAAAAAAAAAAAAAAPC/" + ], + [ + 853, + 1, + "insert", + { + "characters": "content" + }, + "BwAAAIMGAAAAAAAAhAYAAAAAAAAAAAAAhAYAAAAAAACFBgAAAAAAAAAAAACFBgAAAAAAAIYGAAAAAAAAAAAAAIYGAAAAAAAAhwYAAAAAAAAAAAAAhwYAAAAAAACIBgAAAAAAAAAAAACIBgAAAAAAAIkGAAAAAAAAAAAAAIkGAAAAAAAAigYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwYAAAAAAACDBgAAAAAAAAAAAAAAAPC/" + ], + [ + 861, + 1, + "left_delete", + null, + "AQAAAEAGAAAAAAAAQAYAAAAAAAABAAAAPQ", + "AQAAAAAAAAABAAAAQQYAAAAAAABBBgAAAAAAAAAAAAAAAPC/" + ], + [ + 865, + 1, + "insert", + { + "characters": "\n" + }, + "BAAAAIkGAAAAAAAAigYAAAAAAAAAAAAAigYAAAAAAACOBgAAAAAAAAAAAACKBgAAAAAAAIoGAAAAAAAABAAAACAgICCKBgAAAAAAAJIGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQYAAAAAAACJBgAAAAAAAAAAAAAAAPC/" + ], + [ + 868, + 1, + "insert", + { + "characters": "\n," + }, + "BQAAAIkGAAAAAAAAigYAAAAAAAAAAAAAigYAAAAAAACOBgAAAAAAAAAAAACKBgAAAAAAAIoGAAAAAAAABAAAACAgICCKBgAAAAAAAJIGAAAAAAAAAAAAAJIGAAAAAAAAkwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQYAAAAAAACJBgAAAAAAAP///////+9/" + ], + [ + 869, + 1, + "insert", + { + "characters": " " + }, + "AQAAAJMGAAAAAAAAlAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkwYAAAAAAACTBgAAAAAAAAAAAAAAAPC/" + ], + [ + 879, + 1, + "paste", + null, + "AQAAAJQGAAAAAAAAmwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlAYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" + ], + [ + 881, + 1, + "right_delete", + null, + "AQAAAJQGAAAAAAAAlAYAAAAAAAAHAAAAY3JlYXRlZA", + "AQAAAAAAAAABAAAAmwYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" + ], + [ + 882, + 1, + "insert", + { + "characters": "created=" + }, + "CAAAAJQGAAAAAAAAlQYAAAAAAAAAAAAAlQYAAAAAAACWBgAAAAAAAAAAAACWBgAAAAAAAJcGAAAAAAAAAAAAAJcGAAAAAAAAmAYAAAAAAAAAAAAAmAYAAAAAAACZBgAAAAAAAAAAAACZBgAAAAAAAJoGAAAAAAAAAAAAAJoGAAAAAAAAmwYAAAAAAAAAAAAAmwYAAAAAAACcBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlAYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" + ], + [ + 883, + 1, + "insert", + { + "characters": "row.c" + }, + "BQAAAJwGAAAAAAAAnQYAAAAAAAAAAAAAnQYAAAAAAACeBgAAAAAAAAAAAACeBgAAAAAAAJ8GAAAAAAAAAAAAAJ8GAAAAAAAAoAYAAAAAAAAAAAAAoAYAAAAAAAChBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAnAYAAAAAAACcBgAAAAAAAAAAAAAAAPC/" + ], + [ + 884, + 1, + "insert_completion", + { + "completion": "created", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "created" + }, + "AgAAAKAGAAAAAAAAoAYAAAAAAAABAAAAY6AGAAAAAAAApwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoQYAAAAAAAChBgAAAAAAAAAAAAAAAPC/" + ], + [ + 885, + 1, + "insert", + { + "characters": "," + }, + "AQAAAKcGAAAAAAAAqAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApwYAAAAAAACnBgAAAAAAAAAAAAAAAPC/" + ], + [ + 886, + 1, + "insert", + { + "characters": " no" + }, + "AwAAAKgGAAAAAAAAqQYAAAAAAAAAAAAAqQYAAAAAAACqBgAAAAAAAAAAAACqBgAAAAAAAKsGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqAYAAAAAAACoBgAAAAAAAAAAAAAAAPC/" + ], + [ + 887, + 1, + "insert", + { + "characters": "tified=o" + }, + "CAAAAKsGAAAAAAAArAYAAAAAAAAAAAAArAYAAAAAAACtBgAAAAAAAAAAAACtBgAAAAAAAK4GAAAAAAAAAAAAAK4GAAAAAAAArwYAAAAAAAAAAAAArwYAAAAAAACwBgAAAAAAAAAAAACwBgAAAAAAALEGAAAAAAAAAAAAALEGAAAAAAAAsgYAAAAAAAAAAAAAsgYAAAAAAACzBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAPC/" + ], + [ + 888, + 1, + "left_delete", + null, + "AQAAALIGAAAAAAAAsgYAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAswYAAAAAAACzBgAAAAAAAAAAAAAAAPC/" + ], + [ + 889, + 1, + "insert", + { + "characters": "row.n" + }, + "BQAAALIGAAAAAAAAswYAAAAAAAAAAAAAswYAAAAAAAC0BgAAAAAAAAAAAAC0BgAAAAAAALUGAAAAAAAAAAAAALUGAAAAAAAAtgYAAAAAAAAAAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAsgYAAAAAAACyBgAAAAAAAAAAAAAAAPC/" + ], + [ + 890, + 1, + "insert_completion", + { + "completion": "notified", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "notified" + }, + "AgAAALYGAAAAAAAAtgYAAAAAAAABAAAAbrYGAAAAAAAAvgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtwYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" + ], + [ + 891, + 1, + "insert", + { + "characters": "," + }, + "AQAAAL4GAAAAAAAAvwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgYAAAAAAAC+BgAAAAAAAAAAAAAAAPC/" + ], + [ + 892, + 1, + "insert", + { + "characters": " pul" + }, + "BAAAAL8GAAAAAAAAwAYAAAAAAAAAAAAAwAYAAAAAAADBBgAAAAAAAAAAAADBBgAAAAAAAMIGAAAAAAAAAAAAAMIGAAAAAAAAwwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvwYAAAAAAAC/BgAAAAAAAAAAAAAAAPC/" + ], + [ + 893, + 1, + "left_delete", + null, + "AQAAAMIGAAAAAAAAwgYAAAAAAAABAAAAbA", + "AQAAAAAAAAABAAAAwwYAAAAAAADDBgAAAAAAAAAAAAAAAPC/" + ], + [ + 894, + 1, + "insert", + { + "characters": "blished=row" + }, + "CwAAAMIGAAAAAAAAwwYAAAAAAAAAAAAAwwYAAAAAAADEBgAAAAAAAAAAAADEBgAAAAAAAMUGAAAAAAAAAAAAAMUGAAAAAAAAxgYAAAAAAAAAAAAAxgYAAAAAAADHBgAAAAAAAAAAAADHBgAAAAAAAMgGAAAAAAAAAAAAAMgGAAAAAAAAyQYAAAAAAAAAAAAAyQYAAAAAAADKBgAAAAAAAAAAAADKBgAAAAAAAMsGAAAAAAAAAAAAAMsGAAAAAAAAzAYAAAAAAAAAAAAAzAYAAAAAAADNBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwgYAAAAAAADCBgAAAAAAAAAAAAAAAPC/" + ], + [ + 895, + 1, + "insert", + { + "characters": ".p" + }, + "AgAAAM0GAAAAAAAAzgYAAAAAAAAAAAAAzgYAAAAAAADPBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzQYAAAAAAADNBgAAAAAAAAAAAAAAAPC/" + ], + [ + 896, + 1, + "insert_completion", + { + "completion": "published", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "published" + }, + "AgAAAM4GAAAAAAAAzgYAAAAAAAABAAAAcM4GAAAAAAAA1wYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzwYAAAAAAADPBgAAAAAAAAAAAAAAAPC/" + ], + [ + 900, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADiBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIHB1Ymxpc2hfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5pbnNlcnQoYXNkaWN0KGNvbW1lbnQpKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KCkKICAgICAgICAuc29ydChsYW1iZGEgcm93OiByb3cucHVibGlzaGVkKQogICAgKQoKCmRlZiBjb3VudF9wdWJsaXNoZWRfY29tbWVudHModXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkpLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgcm93ID0gZGIoKS5jb21tZW50Lmluc2VydCgKICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9ZGF0ZXRpbWUubm93KCksCiAgICAgICAgbm90aWZpZWQ9Tm9uZSwKICAgICAgICBwdWJsaXNoZWQ9Tm9uZSwKICAgICkKCiAgICByZXR1cm4gQ29tbWVudChpZD1yb3cuaWQsIHVybD1yb3cudXJsLCBhdXRob3JfbmFtZT1yb3cuYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyPXJvdy5hdXRob3JfZ3JhdmF0YXIsIGNvbnRlbnQ9cm93LmNvbnRlbnQKICAgICAgICAsIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsIG5vdGlmaWVkPXJvdy5ub3RpZmllZCwgcHVibGlzaGVkPXJvdy5wdWJsaXNoZWQKICAgICAgICApCgAAAAAAAAAAHwcAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gYAAAAAAADiBgAAAAAAAAAAAAAAqIVA" + ], + [ + 907, + 1, + "insert", + { + "characters": "[" + }, + "AQAAAF4BAAAAAAAAXwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXgEAAAAAAABeAQAAAAAAAAAAAAAAAPC/" + ], + [ + 911, + 1, + "insert", + { + "characters": "]" + }, + "AQAAAG4BAAAAAAAAbwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" + ], + [ + 915, + 1, + "insert", + { + "characters": "[" + }, + "AQAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2QEAAAAAAADZAQAAAAAAAAAAAAAAAPC/" + ], + [ + 921, + 1, + "insert", + { + "characters": "]" + }, + "AQAAAOkBAAAAAAAA6gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6QEAAAAAAADpAQAAAAAAAAAAAAAAAPC/" + ], + [ + 935, + 1, + "insert", + { + "characters": "bulk-" + }, + "BQAAAFcBAAAAAAAAWAEAAAAAAAAAAAAAWAEAAAAAAABZAQAAAAAAAAAAAABZAQAAAAAAAFoBAAAAAAAAAAAAAFoBAAAAAAAAWwEAAAAAAAAAAAAAWwEAAAAAAABcAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVwEAAAAAAABXAQAAAAAAAAAAAAAAAPC/" + ], + [ + 936, + 1, + "left_delete", + null, + "AQAAAFsBAAAAAAAAWwEAAAAAAAABAAAALQ", + "AQAAAAAAAAABAAAAXAEAAAAAAABcAQAAAAAAAAAAAAAAAPC/" + ], + [ + 937, + 1, + "insert", + { + "characters": "_" + }, + "AQAAAFsBAAAAAAAAXAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWwEAAAAAAABbAQAAAAAAAAAAAAAAAPC/" + ], + [ + 949, + 1, + "paste", + null, + "AgAAANcBAAAAAAAA4gEAAAAAAAAAAAAA4gEAAAAAAADiAQAAAAAAAAYAAABpbnNlcnQ", + "AQAAAAAAAAABAAAA1wEAAAAAAADdAQAAAAAAAAAAAAAAAPC/" + ], + [ + 952, + 1, + "insert", + { + "characters": "\ndb" + }, + "BAAAAHUBAAAAAAAAdgEAAAAAAAAAAAAAdgEAAAAAAAB6AQAAAAAAAAAAAAB6AQAAAAAAAHsBAAAAAAAAAAAAAHsBAAAAAAAAfAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdQEAAAAAAAB1AQAAAAAAAAAAAAAAAPC/" + ], + [ + 953, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAHwBAAAAAAAAfgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfAEAAAAAAAB8AQAAAAAAAAAAAAAAAPC/" + ], + [ + 954, + 1, + "insert", + { + "characters": "à." + }, + "AgAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfQEAAAAAAAB9AQAAAAAAAAAAAAAAAPC/" + ], + [ + 955, + 2, + "left_delete", + null, + "AgAAAH4BAAAAAAAAfgEAAAAAAAABAAAALn0BAAAAAAAAfQEAAAAAAAACAAAAw6A", + "AQAAAAAAAAABAAAAfwEAAAAAAAB/AQAAAAAAAAAAAAAAAPC/" + ], + [ + 957, + 1, + "insert", + { + "characters": ".co" + }, + "AwAAAH4BAAAAAAAAfwEAAAAAAAAAAAAAfwEAAAAAAACAAQAAAAAAAAAAAACAAQAAAAAAAIEBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgEAAAAAAAB+AQAAAAAAAAAAAAAAAPC/" + ], + [ + 958, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.commit\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":16,\"character\":11},\"symbolLabel\":\"commit\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"commit\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "commit" + }, + "AgAAAH8BAAAAAAAAfwEAAAAAAAACAAAAY29/AQAAAAAAAIUBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQEAAAAAAACBAQAAAAAAAAAAAAAAAPC/" + ], + [ + 959, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAIUBAAAAAAAAhwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQEAAAAAAACFAQAAAAAAAAAAAAAAAPC/" + ], + [ + 970, + 1, + "reindent", + null, + "AQAAAAgCAAAAAAAADAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACAIAAAAAAAAIAgAAAAAAAAAAAAAAAAAA" + ], + [ + 971, + 1, + "paste", + null, + "AQAAAAwCAAAAAAAAGQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADAIAAAAAAAAMAgAAAAAAAAAAAAAAAPC/" + ], + [ + 976, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAHECAAAAAAAAcgIAAAAAAAAAAAAAcgIAAAAAAAB2AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" + ], + [ + 977, + 1, + "paste", + null, + "AQAAAHYCAAAAAAAAgwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgIAAAAAAAB2AgAAAAAAAAAAAAAAAPC/" + ], + [ + 982, + 1, + "reindent", + null, + "AQAAAC4GAAAAAAAAMgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALgYAAAAAAAAuBgAAAAAAAAAAAAAAAPC/" + ], + [ + 983, + 1, + "paste", + null, + "AQAAADIGAAAAAAAAPwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" + ], + [ + 986, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAABzBwAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50LmJ1bGtfaW5zZXJ0KFthc2RpY3QoY29tbWVudCldKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBwdWJsaXNoX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBjb21tZW50LnB1Ymxpc2hlZCA9IGRhdGV0aW1lLm5vdygpCiAgICBkYigpLmNvbW1lbnQuYnVsa19pbnNlcnQoW2FzZGljdChjb21tZW50KV0pCiAgICBkYigpLmNvbW1pdCgpCgpkZWYgZGVsZXRlX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBkYigpKGRiKCkuY29tbWVudC5pZCA9PSBjb21tZW50LmlkKS5kZWxldGUoKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBmaW5kX25vdF9ub3RpZmllZF9jb21tZW50cygpOgogICAgcmV0dXJuIGRiKCkoZGIoKS5jb21tZW50Lm5vdGlmaWVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5zZWxlY3QoKQoKCmRlZiBmaW5kX3B1Ymxpc2hlZF9jb21tZW50c19ieV91cmwodXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkpCiAgICAgICAgLnNlbGVjdCgpCiAgICAgICAgLnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCkKICAgICkKCgpkZWYgY291bnRfcHVibGlzaGVkX2NvbW1lbnRzKHVybCk6CiAgICByZXR1cm4gKAogICAgICAgIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpKS5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkuY291bnQoKQogICAgKQoKCmRlZiBjcmVhdGVfY29tbWVudCh1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlKToKICAgIHJvdyA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICBkYigpLmNvbW1pdCgpCiAgICByZXR1cm4gQ29tbWVudCgKICAgICAgICBpZD1yb3cuaWQsCiAgICAgICAgdXJsPXJvdy51cmwsCiAgICAgICAgYXV0aG9yX25hbWU9cm93LmF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9cm93LmF1dGhvcl9ncmF2YXRhciwKICAgICAgICBjb250ZW50PXJvdy5jb250ZW50LAogICAgICAgIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsCiAgICAgICAgbm90aWZpZWQ9cm93Lm5vdGlmaWVkLAogICAgICAgIHB1Ymxpc2hlZD1yb3cucHVibGlzaGVkLAogICAgKQoAAAAAAAAAAHQHAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPwYAAAAAAAA/BgAAAAAAAAAAAAAAAPC/" + ], + [ + 999, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAEUBAAAAAAAARgEAAAAAAAAAAAAARgEAAAAAAABKAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARQEAAAAAAABFAQAAAAAAAAAAAAAA0HRA" + ], + [ + 1001, + 1, + "", + null, + "AQAAAEoBAAAAAAAAgQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1005, + 1, + "insert", + { + "characters": "()" + }, + "AgAAAEwBAAAAAAAATQEAAAAAAAAAAAAATQEAAAAAAABOAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATAEAAAAAAABMAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1008, + 1, + "insert", + { + "characters": ".comment" + }, + "CQAAAFEBAAAAAAAAUgEAAAAAAAAAAAAAUgEAAAAAAABSAQAAAAAAAAEAAABbUgEAAAAAAABTAQAAAAAAAAAAAABTAQAAAAAAAFQBAAAAAAAAAAAAAFQBAAAAAAAAVQEAAAAAAAAAAAAAVQEAAAAAAABWAQAAAAAAAAAAAABWAQAAAAAAAFcBAAAAAAAAAAAAAFcBAAAAAAAAWAEAAAAAAAAAAAAAWAEAAAAAAABZAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1011, + 1, + "insert", + { + "characters": "()" + }, + "AgAAAFEBAAAAAAAAUgEAAAAAAAAAAAAAUgEAAAAAAABTAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQEAAAAAAABRAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1016, + 1, + "right_delete", + null, + "AQAAAFsBAAAAAAAAWwEAAAAAAAAKAAAAdGFibGVuYW1lXQ", + "AQAAAAAAAAABAAAAWwEAAAAAAABlAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1019, + 1, + "insert", + { + "characters": "comment." + }, + "CAAAAGMBAAAAAAAAZAEAAAAAAAAAAAAAZAEAAAAAAABlAQAAAAAAAAAAAABlAQAAAAAAAGYBAAAAAAAAAAAAAGYBAAAAAAAAZwEAAAAAAAAAAAAAZwEAAAAAAABoAQAAAAAAAAAAAABoAQAAAAAAAGkBAAAAAAAAAAAAAGkBAAAAAAAAagEAAAAAAAAAAAAAagEAAAAAAABrAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwEAAAAAAABjAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1024, + 1, + "right_delete", + null, + "AQAAAHYBAAAAAAAAdgEAAAAAAAATAAAAKip7ZmllbGRuYW1lOnZhbHVlfQ", + "AQAAAAAAAAABAAAAdgEAAAAAAACJAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1025, + 1, + "insert", + { + "characters": "notii" + }, + "BQAAAHYBAAAAAAAAdwEAAAAAAAAAAAAAdwEAAAAAAAB4AQAAAAAAAAAAAAB4AQAAAAAAAHkBAAAAAAAAAAAAAHkBAAAAAAAAegEAAAAAAAAAAAAAegEAAAAAAAB7AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgEAAAAAAAB2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 1026, + 1, + "left_delete", + null, + "AQAAAHoBAAAAAAAAegEAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAAewEAAAAAAAB7AQAAAAAAAAAAAAAAAPC/" + ], + [ + 1027, + 1, + "insert", + { + "characters": "fied=" + }, + "BQAAAHoBAAAAAAAAewEAAAAAAAAAAAAAewEAAAAAAAB8AQAAAAAAAAAAAAB8AQAAAAAAAH0BAAAAAAAAAAAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAegEAAAAAAAB6AQAAAAAAAAAAAAAAAPC/" + ], + [ + 1030, + 1, + "cut", + null, + "AQAAADcBAAAAAAAANwEAAAAAAAAOAAAAZGF0ZXRpbWUubm93KCk", + "AQAAAAAAAAABAAAARQEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" + ], + [ + 1033, + 1, + "paste", + null, + "AQAAAHEBAAAAAAAAfwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcQEAAAAAAABxAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1036, + 1, + "right_delete", + null, + "AQAAAIUBAAAAAAAAhQEAAAAAAAArAAAAZGIoKS5jb21tZW50LmJ1bGtfaW5zZXJ0KFthc2RpY3QoY29tbWVudCldKQ", + "AQAAAAAAAAABAAAAsAEAAAAAAACFAQAAAAAAAAAAAAAAAEJA" + ], + [ + 1038, + 1, + "left_delete", + null, + "AQAAAIABAAAAAAAAgAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAgQEAAAAAAACBAQAAAAAAAAAAAAAAAAAA" + ], + [ + 1041, + 1, + "right_delete", + null, + "AQAAACQBAAAAAAAAJAEAAAAAAAATAAAAY29tbWVudC5ub3RpZmllZCA9IA", + "AQAAAAAAAAABAAAANwEAAAAAAAAkAQAAAAAAAAAAAAAAAEJA" + ], + [ + 1043, + 1, + "left_delete", + null, + "AQAAAB8BAAAAAAAAHwEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAAAA" + ], + [ + 1045, + 1, + "trim_trailing_white_space", + null, + "AgAAAGwBAAAAAAAAbAEAAAAAAAAEAAAAICAgIB8BAAAAAAAAHwEAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAHwEAAAAAAAAfAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1053, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAKMBAAAAAAAApAEAAAAAAAAAAAAApAEAAAAAAACoAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAowEAAAAAAACjAQAAAAAAAP///////+9/" + ], + [ + 1054, + 1, + "paste", + null, + "AQAAAKgBAAAAAAAA7AEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqAEAAAAAAACoAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1065, + 1, + "paste", + null, + "AgAAANQBAAAAAAAA3QEAAAAAAAAAAAAA3QEAAAAAAADdAQAAAAAAAAgAAABub3RpZmllZA", + "AQAAAAAAAAABAAAA1AEAAAAAAADcAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1068, + 2, + "left_delete", + null, + "AgAAAO4BAAAAAAAA7gEAAAAAAABWAAAAICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5idWxrX2luc2VydChbYXNkaWN0KGNvbW1lbnQpXSntAQAAAAAAAO0BAAAAAAAAAQAAAAo", + "AQAAAAAAAAABAAAARAIAAAAAAADuAQAAAAAAAAAAAAAAAPC/" + ], + [ + 1074, + 1, + "insert", + { + "characters": "_" + }, + "AQAAAD4CAAAAAAAAPwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPgIAAAAAAAA+AgAAAAAAAAAAAAAAAPC/" + ], + [ + 1079, + 1, + "left_delete", + null, + "AQAAAD4CAAAAAAAAPgIAAAAAAAABAAAAXw", + "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAA4GlA" + ], + [ + 1083, + 1, + "right_delete", + null, + "AQAAALoBAAAAAAAAugEAAAAAAAABAAAAXw", + "AQAAAAAAAAABAAAAugEAAAAAAAC6AQAAAAAAAAAAAAAAAPC/" + ], + [ + 1085, + 1, + "right_delete", + null, + "AQAAADYBAAAAAAAANgEAAAAAAAABAAAAXw", + "AQAAAAAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAwGhA" + ], + [ + 1092, + 1, + "right_delete", + null, + "AQAAACoAAAAAAAAAKgAAAAAAAAAeAAAAZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0", + "AQAAAAAAAAABAAAASAAAAAAAAAAqAAAAAAAAAAAAAAAAAAAA" + ], + [ + 1093, + 1, + "left_delete", + null, + "AQAAACkAAAAAAAAAKQAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAPC/" + ], + [ + 1108, + 1, + "toggle_breakpoint", + null, + "AQAAAO0DAAAAAAAAJwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA9AMAAAAAAAD0AwAAAAAAAAAAAAAAAPC/" + ], + [ + 1120, + 1, + "toggle_breakpoint", + null, + "AQAAAO0DAAAAAAAA7QMAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGYyYzA0YTBkIC8vCg", + "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1127, + 1, + "insert", + { + "characters": "!" + }, + "AgAAADsEAAAAAAAAPAQAAAAAAAAAAAAAPAQAAAAAAAA8BAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAOwQAAAAAAAA8BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1131, + 1, + "insert", + { + "characters": "!" + }, + "AgAAAIUEAAAAAAAAhgQAAAAAAAAAAAAAhgQAAAAAAACGBAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAhQQAAAAAAACGBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1136, + 1, + "insert", + { + "characters": "def" + }, + "AwAAAJ0EAAAAAAAAngQAAAAAAAAAAAAAngQAAAAAAACfBAAAAAAAAAAAAACfBAAAAAAAAKAEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAnQQAAAAAAACdBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1137, + 1, + "insert", + { + "characters": " " + }, + "AQAAAKAEAAAAAAAAoQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoAQAAAAAAACgBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1138, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAKEEAAAAAAAAogQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1141, + 1, + "insert", + { + "characters": " " + }, + "AQAAAKAEAAAAAAAAoQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoAQAAAAAAACgBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1142, + 1, + "insert", + { + "characters": "\n\n" + }, + "AgAAAKEEAAAAAAAAogQAAAAAAAAAAAAAogQAAAAAAACjBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1143, + 1, + "paste", + null, + "AQAAAKMEAAAAAAAAJgUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAowQAAAAAAACjBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1147, + 1, + "insert", + { + "characters": "find_" + }, + "BQAAAKEEAAAAAAAAogQAAAAAAAAAAAAAogQAAAAAAACjBAAAAAAAAAAAAACjBAAAAAAAAKQEAAAAAAAAAAAAAKQEAAAAAAAApQQAAAAAAAAAAAAApQQAAAAAAACmBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1148, + 1, + "insert", + { + "characters": "lastè" + }, + "BQAAAKYEAAAAAAAApwQAAAAAAAAAAAAApwQAAAAAAACoBAAAAAAAAAAAAACoBAAAAAAAAKkEAAAAAAAAAAAAAKkEAAAAAAAAqgQAAAAAAAAAAAAAqgQAAAAAAACrBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApgQAAAAAAACmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1149, + 1, + "left_delete", + null, + "AQAAAKoEAAAAAAAAqgQAAAAAAAACAAAAw6g", + "AQAAAAAAAAABAAAAqwQAAAAAAACrBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1150, + 1, + "insert", + { + "characters": "_" + }, + "AQAAAKoEAAAAAAAAqwQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqgQAAAAAAACqBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1151, + 3, + "left_delete", + null, + "AwAAAKoEAAAAAAAAqgQAAAAAAAABAAAAX6kEAAAAAAAAqQQAAAAAAAABAAAAdKgEAAAAAAAAqAQAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAAqwQAAAAAAACrBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1152, + 1, + "insert", + { + "characters": "test_publ" + }, + "CQAAAKgEAAAAAAAAqQQAAAAAAAAAAAAAqQQAAAAAAACqBAAAAAAAAAAAAACqBAAAAAAAAKsEAAAAAAAAAAAAAKsEAAAAAAAArAQAAAAAAAAAAAAArAQAAAAAAACtBAAAAAAAAAAAAACtBAAAAAAAAK4EAAAAAAAAAAAAAK4EAAAAAAAArwQAAAAAAAAAAAAArwQAAAAAAACwBAAAAAAAAAAAAACwBAAAAAAAALEEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqAQAAAAAAACoBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1153, + 1, + "insert", + { + "characters": "ished_co" + }, + "CAAAALEEAAAAAAAAsgQAAAAAAAAAAAAAsgQAAAAAAACzBAAAAAAAAAAAAACzBAAAAAAAALQEAAAAAAAAAAAAALQEAAAAAAAAtQQAAAAAAAAAAAAAtQQAAAAAAAC2BAAAAAAAAAAAAAC2BAAAAAAAALcEAAAAAAAAAAAAALcEAAAAAAAAuAQAAAAAAAAAAAAAuAQAAAAAAAC5BAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAsQQAAAAAAACxBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1159, + 1, + "insert", + { + "characters": "recent" + }, + "BwAAAKYEAAAAAAAApwQAAAAAAAAAAAAApwQAAAAAAACnBAAAAAAAAAYAAABsYXRlc3SnBAAAAAAAAKgEAAAAAAAAAAAAAKgEAAAAAAAAqQQAAAAAAAAAAAAAqQQAAAAAAACqBAAAAAAAAAAAAACqBAAAAAAAAKsEAAAAAAAAAAAAAKsEAAAAAAAArAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAArAQAAAAAAACmBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1162, + 1, + "insert", + { + "characters": "mments" + }, + "BgAAALkEAAAAAAAAugQAAAAAAAAAAAAAugQAAAAAAAC7BAAAAAAAAAAAAAC7BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuQQAAAAAAAC5BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1163, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAL8EAAAAAAAAwQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvwQAAAAAAAC/BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1165, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAMEEAAAAAAAAwgQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQQAAAAAAADBBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1177, + 1, + "paste", + null, + "AQAAAMMEAAAAAAAAXAUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwwQAAAAAAADDBAAAAAAAAAAAAAAAAPC/" + ], + [ + 1183, + 1, + "right_delete", + null, + "AQAAAN0EAAAAAAAA3QQAAAAAAAAdAAAAKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmICg", + "AQAAAAAAAAABAAAA3QQAAAAAAAD6BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1188, + 1, + "right_delete", + null, + "AQAAAPsEAAAAAAAA+wQAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAA+wQAAAAAAAD7BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1195, + 1, + "", + null, + "AQAAAA0FAAAAAAAAGwUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADQUAAAAAAAANBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1198, + 1, + "insert", + { + "characters": "10" + }, + "AwAAABkFAAAAAAAAGgUAAAAAAAAAAAAAGgUAAAAAAAAaBQAAAAAAAAEAAAAyGgUAAAAAAAAbBQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGgUAAAAAAAAZBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1203, + 1, + "", + null, + "AQAAAJcDAAAAAAAAnwMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlwMAAAAAAACXAwAAAAAAAAAAAAAAAPC/" + ], + [ + 1210, + 1, + "paste", + null, + "AQAAAJ8DAAAAAAAArAMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAnwMAAAAAAACfAwAAAAAAAAAAAAAAAPC/" + ], + [ + 1211, + 1, + "insert", + { + "characters": "published" + }, + "CQAAAKwDAAAAAAAArQMAAAAAAAAAAAAArQMAAAAAAACuAwAAAAAAAAAAAACuAwAAAAAAAK8DAAAAAAAAAAAAAK8DAAAAAAAAsAMAAAAAAAAAAAAAsAMAAAAAAACxAwAAAAAAAAAAAACxAwAAAAAAALIDAAAAAAAAAAAAALIDAAAAAAAAswMAAAAAAAAAAAAAswMAAAAAAAC0AwAAAAAAAAAAAAC0AwAAAAAAALUDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAArAMAAAAAAACsAwAAAAAAAAAAAAAAAPC/" + ], + [ + 1215, + 1, + "right_delete", + null, + "AQAAAL8DAAAAAAAAvwMAAAAAAAAgAAAALnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCk", + "AQAAAAAAAAABAAAAvwMAAAAAAADfAwAAAAAAAP///////+9/" + ], + [ + 1217, + 1, + "left_delete", + null, + "AQAAALYDAAAAAAAAtgMAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAtwMAAAAAAAC3AwAAAAAAAAAAAAAAAAAA" + ], + [ + 1219, + 1, + "trim_trailing_white_space", + null, + "AgAAAM4FAAAAAAAAzgUAAAAAAAABAAAAILYDAAAAAAAAtgMAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAtgMAAAAAAAC2AwAAAAAAAAAAAAAAAPC/" + ], + [ + 1225, + 1, + "paste", + null, + "AQAAAAIFAAAAAAAAIAUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAgUAAAAAAAACBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1226, + 1, + "insert", + { + "characters": "," + }, + "AQAAACAFAAAAAAAAIQUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIAUAAAAAAAAgBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1227, + 1, + "insert", + { + "characters": " " + }, + "AQAAACEFAAAAAAAAIgUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIQUAAAAAAAAhBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1230, + 1, + "insert", + { + "characters": "~" + }, + "AQAAAAoFAAAAAAAACwUAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACgUAAAAAAAAKBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1234, + 1, + "right_delete", + null, + "AQAAADwFAAAAAAAAPAUAAAAAAAAgAAAALnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCk", + "AQAAAAAAAAABAAAAPAUAAAAAAABcBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1236, + 1, + "left_delete", + null, + "AQAAADMFAAAAAAAAMwUAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAANAUAAAAAAAA0BQAAAAAAAAAAAAAAAAAA" + ], + [ + 1241, + 1, + "left_delete", + null, + "AQAAAEIFAAAAAAAAQgUAAAAAAACEAAAACiAgICAgICAgICAgIENvbW1lbnQuc2VsZWN0KCkKICAgICAgICAgICAgLndoZXJlKENvbW1lbnQucHVibGlzaGVkKQogICAgICAgICAgICAub3JkZXJfYnkoLUNvbW1lbnQucHVibGlzaGVkKQogICAgICAgICAgICAubGltaXQoMTAp", + "AQAAAAAAAAABAAAAxgUAAAAAAABCBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1243, + 1, + "left_delete", + null, + "AQAAAEIFAAAAAAAAQgUAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAQwUAAAAAAABDBQAAAAAAAAAAAAAAAAAA" + ], + [ + 1245, + 1, + "trim_trailing_white_space", + null, + "AQAAADMFAAAAAAAAMwUAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAQgUAAAAAAABCBQAAAAAAAAAAAAAAAPC/" + ], + [ + 1251, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAADWBwAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKCmRlZiBmaW5kX2NvbW1lbnRfYnlfaWQoY29tbWVudF9pZCk6CiAgICByZXR1cm4gZGIoKS5jb21tZW50KGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgZGIoKShkYigpLmNvbW1lbnQuaWQgPT0gY29tbWVudC5pZCkudXBkYXRlKG5vdGlmaWVkPWRhdGV0aW1lLm5vdygpKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBwdWJsaXNoX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBkYigpKGRiKCkuY29tbWVudC5pZCA9PSBjb21tZW50LmlkKS51cGRhdGUocHVibGlzaGVkPWRhdGV0aW1lLm5vdygpKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCiAgICBkYigpLmNvbW1pdCgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KG9yZGVyYnk9ZGIoKS5jb21tZW50LnB1Ymxpc2hlZCkKICAgICkKCgpkZWYgY291bnRfcHVibGlzaGVkX2NvbW1lbnRzKHVybCk6CiAgICByZXR1cm4gKAogICAgICAgIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkICE9IE5vbmUpKS5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkuY291bnQoKQogICAgKQoKZGVmIGZpbmRfcmVjZW50X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkKICAgICAgICAuc2VsZWN0KG9yZGVyYnk9fmRiKCkuY29tbWVudC5wdWJsaXNoZWQsIGxpbWl0Ynk9KDAsIDEwKSkKICAgICkKCmRlZiBjcmVhdGVfY29tbWVudCh1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlKToKICAgIHJvdyA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICBkYigpLmNvbW1pdCgpCiAgICByZXR1cm4gQ29tbWVudCgKICAgICAgICBpZD1yb3cuaWQsCiAgICAgICAgdXJsPXJvdy51cmwsCiAgICAgICAgYXV0aG9yX25hbWU9cm93LmF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9cm93LmF1dGhvcl9ncmF2YXRhciwKICAgICAgICBjb250ZW50PXJvdy5jb250ZW50LAogICAgICAgIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsCiAgICAgICAgbm90aWZpZWQ9cm93Lm5vdGlmaWVkLAogICAgICAgIHB1Ymxpc2hlZD1yb3cucHVibGlzaGVkLAogICAgKQoAAAAAAAAAAMIHAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlgQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" + ], + [ + 1254, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAACkAAAAAAAAAKgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKQAAAAAAAAApAAAAAAAAAAAAAAAAAPC/" + ], + [ + 1255, + 1, + "paste", + null, + "AQAAACoAAAAAAAAATwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAPC/" + ], + [ + 1256, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAE8AAAAAAAAAUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + ], + [ + 1260, + 1, + "", + null, + "AgAAADwAAAAAAAAAUAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAABMAAABjb25zaWRlci11c2luZy13aXRo", + "AQAAAAAAAAABAAAAPAAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "stacosys/service/rssfeed.py", + "settings": + { + "buffer_size": 1754, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 15, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAAbAAAAAAAAABtAAAAAAAAAAAAAABtAAAAAAAAAG4AAAAAAAAAAAAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABwAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 1, + "insert", + { + "characters": " stacs" + }, + "BgAAAHAAAAAAAAAAcQAAAAAAAAAAAAAAcQAAAAAAAAByAAAAAAAAAAAAAAByAAAAAAAAAHMAAAAAAAAAAAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB1AAAAAAAAAAAAAAB1AAAAAAAAAHYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcAAAAAAAAABwAAAAAAAAAAAAAAAAAPC/" + ], + [ + 17, + 1, + "left_delete", + null, + "AQAAAHUAAAAAAAAAdQAAAAAAAAABAAAAcw", + "AQAAAAAAAAABAAAAdgAAAAAAAAB2AAAAAAAAAAAAAAAAAPC/" + ], + [ + 18, + 1, + "insert", + { + "characters": "osys.db" + }, + "BwAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAB6AAAAAAAAAAAAAAB6AAAAAAAAAHsAAAAAAAAAAAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdQAAAAAAAAB1AAAAAAAAAAAAAAAAAPC/" + ], + [ + 19, + 1, + "insert", + { + "characters": "." + }, + "AQAAAHwAAAAAAAAAfQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 20, + 1, + "left_delete", + null, + "AQAAAHwAAAAAAAAAfAAAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAfQAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + ], + [ + 21, + 1, + "insert", + { + "characters": " import" + }, + "BwAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 22, + 1, + "insert", + { + "characters": " dai" + }, + "BAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "left_delete", + null, + "AQAAAIYAAAAAAAAAhgAAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 24, + 1, + "insert", + { + "characters": "o" + }, + "AQAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "paste", + null, + "AQAAAOQCAAAAAAAAAgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5AIAAAAAAADkAgAAAAAAAAAAAAAAAPC/" + ], + [ + 39, + 1, + "insert", + { + "characters": "dao/" + }, + "BAAAAOQCAAAAAAAA5QIAAAAAAAAAAAAA5QIAAAAAAADmAgAAAAAAAAAAAADmAgAAAAAAAOcCAAAAAAAAAAAAAOcCAAAAAAAA6AIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5AIAAAAAAADkAgAAAAAAAAAAAAAAAPC/" + ], + [ + 40, + 1, + "left_delete", + null, + "AQAAAOcCAAAAAAAA5wIAAAAAAAABAAAALw", + "AQAAAAAAAAABAAAA6AIAAAAAAADoAgAAAAAAAAAAAAAAAPC/" + ], + [ + 41, + 1, + "insert", + { + "characters": "." + }, + "AQAAAOcCAAAAAAAA6AIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5wIAAAAAAADnAgAAAAAAAAAAAAAAAPC/" + ], + [ + 44, + 1, + "insert", + { + "characters": "):" + }, + "AgAAAAcDAAAAAAAACAMAAAAAAAAAAAAACAMAAAAAAAAJAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABwMAAAAAAAAHAwAAAAAAAAAAAAAAAPC/" + ], + [ + 49, + 1, + "right_delete", + null, + "AQAAAAoDAAAAAAAACgMAAAAAAACNAAAAICAgICAgICAgICAgQ29tbWVudC5zZWxlY3QoKQogICAgICAgICAgICAud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQpCiAgICAgICAgICAgIC5vcmRlcl9ieSgtQ29tbWVudC5wdWJsaXNoZWQpCiAgICAgICAgICAgIC5saW1pdCgxMCkKICAgICAgICAp", + "AQAAAAAAAAABAAAACgMAAAAAAACXAwAAAAAAAAAAAAAAAPC/" + ], + [ + 50, + 2, + "left_delete", + null, + "AgAAAAkDAAAAAAAACQMAAAAAAAABAAAACggDAAAAAAAACAMAAAAAAAABAAAAOg", + "AQAAAAAAAAABAAAACgMAAAAAAAAKAwAAAAAAAAAAAAAAAPC/" + ], + [ + 58, + 1, + "right_delete", + null, + "AQAAAIgAAAAAAAAAiAAAAAAAAAAqAAAAZnJvbSBzdGFjb3N5cy5tb2RlbC5jb21tZW50IGltcG9ydCBDb21tZW50", + "AQAAAAAAAAABAAAAsgAAAAAAAACIAAAAAAAAAAAAAAAAAAAA" + ], + [ + 59, + 1, + "left_delete", + null, + "AQAAAIcAAAAAAAAAhwAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "stacosys/model/comment.py", + "settings": + { + "buffer_size": 421, + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 12, + 1, + "insert", + { + "characters": "#" + }, + "AQAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAAAA" + ], + [ + 17, + 1, + "left_delete", + null, + "AQAAAJ8AAAAAAAAAnwAAAAAAAAAJAAAAQmFzZU1vZGVs", + "AQAAAAAAAAABAAAAnwAAAAAAAACoAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" + ], + [ + 24, + 1, + "paste", + null, + "AQAAANABAAAAAAAAoQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0AEAAAAAAADQAQAAAAAAAAAAAAAAAPC/" + ], + [ + 31, + 1, + "paste", + null, + "AQAAAJAAAAAAAAAAmgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAAAA" + ], + [ + 36, + 1, + "paste", + null, + "AgAAACsAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABqAAAAAAAAAGQAAABmcm9tIHBlZXdlZSBpbXBvcnQgQ2hhckZpZWxkLCBEYXRlVGltZUZpZWxkLCBUZXh0RmllbGQKCiNmcm9tIHN0YWNvc3lzLmRiLmRhdGFiYXNlIGltcG9ydCBCYXNlTW9kZWwK", + "AQAAAAAAAAABAAAAKwAAAAAAAACPAAAAAAAAAAAAAAAAAAAA" + ], + [ + 37, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAGoAAAAAAAAAawAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAagAAAAAAAABqAAAAAAAAAAAAAAAAAPC/" + ], + [ + 41, + 1, + "right_delete", + null, + "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAKQ", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 43, + 1, + "right_delete", + null, + "AQAAAIQAAAAAAAAAhAAAAAAAAAABAAAAKA", + "AQAAAAAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" + ], + [ + 47, + 1, + "insert", + { + "characters": "str" + }, + "BAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACRAAAAAAAAAAsAAABDaGFyRmllbGQoKZEAAAAAAAAAkgAAAAAAAAAAAAAAkgAAAAAAAACTAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAAAAAAAAACbAAAAAAAAAP///////+9/" + ], + [ + 54, + 1, + "insert", + { + "characters": "datetime" + }, + "CQAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACjAAAAAAAAAA8AAABEYXRlVGltZUZpZWxkKCmjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACpAAAAAAAAAAAAAACpAAAAAAAAAKoAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAogAAAAAAAACxAAAAAAAAAP///////+9/" + ], + [ + 62, + 1, + "paste", + null, + "AgAAALoAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAACYAAABEYXRlVGltZUZpZWxkKG51bGw9VHJ1ZSwgZGVmYXVsdD1Ob25lKQ", + "AQAAAAAAAAABAAAAugAAAAAAAADgAAAAAAAAAP///////+9/" + ], + [ + 66, + 1, + "paste", + null, + "AgAAANMAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADbAAAAAAAAACYAAABEYXRlVGltZUZpZWxkKG51bGw9VHJ1ZSwgZGVmYXVsdD1Ob25lKQ", + "AQAAAAAAAAABAAAA0wAAAAAAAAD5AAAAAAAAAP///////+9/" + ], + [ + 70, + 1, + "insert", + { + "characters": "str" + }, + "BAAAAO4AAAAAAAAA7wAAAAAAAAAAAAAA7wAAAAAAAADvAAAAAAAAAAsAAABDaGFyRmllbGQoKe8AAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAADxAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7gAAAAAAAAD5AAAAAAAAAP///////+9/" + ], + [ + 74, + 1, + "insert", + { + "characters": ":" + }, + "AgAAAKAAAAAAAAAAoQAAAAAAAAAAAAAAoQAAAAAAAAChAAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAoAAAAAAAAAChAAAAAAAAAAAAAAAAAPC/" ], [ 78, 1, + "insert", + { + "characters": ":" + }, + "AgAAAI4AAAAAAAAAjwAAAAAAAAAAAAAAjwAAAAAAAACPAAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAjwAAAAAAAACOAAAAAAAAAAAAAAAAAPC/" + ], + [ + 82, + 1, + "insert", + { + "characters": ":" + }, + "AgAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC5AAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAuAAAAAAAAAC5AAAAAAAAAAAAAAAAAPC/" + ], + [ + 85, + 1, + "insert", + { + "characters": ":" + }, + "AgAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADSAAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAA0QAAAAAAAADSAAAAAAAAAAAAAAAAAPC/" + ], + [ + 89, + 1, + "insert", + { + "characters": ":" + }, + "AgAAAOwAAAAAAAAA7QAAAAAAAAAAAAAA7QAAAAAAAADtAAAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAA7AAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" + ], + [ + 92, + 1, + "insert", + { + "characters": ":" + }, + "AgAAAAIBAAAAAAAAAwEAAAAAAAAAAAAAAwEAAAAAAAADAQAAAAAAAAEAAAA9", + "AQAAAAAAAAABAAAAAwEAAAAAAAACAQAAAAAAAAAAAAAAAPC/" + ], + [ + 95, + 1, + "insert", + { + "characters": "str" + }, + "BAAAAAQBAAAAAAAABQEAAAAAAAAAAAAABQEAAAAAAAAFAQAAAAAAABUAAABDaGFyRmllbGQoZGVmYXVsdD0iIikFAQAAAAAAAAYBAAAAAAAAAAAAAAYBAAAAAAAABwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABAEAAAAAAAAZAQAAAAAAAP///////+9/" + ], + [ + 99, + 1, + "insert", + { + "characters": ":" + }, + "AgAAABwBAAAAAAAAHQEAAAAAAAAAAAAAHQEAAAAAAAAdAQAAAAAAABcAAAA9IENoYXJGaWVsZChkZWZhdWx0PSIiKQ", + "AQAAAAAAAAABAAAAHAEAAAAAAAAzAQAAAAAAAP///////+9/" + ], + [ + 100, + 1, + "insert", + { + "characters": " str" + }, + "BAAAAB0BAAAAAAAAHgEAAAAAAAAAAAAAHgEAAAAAAAAfAQAAAAAAAAAAAAAfAQAAAAAAACABAAAAAAAAAAAAACABAAAAAAAAIQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHQEAAAAAAAAdAQAAAAAAAAAAAAAAAPC/" + ], + [ + 105, + 1, + "insert", + { + "characters": ":" + }, + "AgAAAC4BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvAQAAAAAAAA0AAAA9IFRleHRGaWVsZCgp", + "AQAAAAAAAAABAAAALgEAAAAAAAA7AQAAAAAAAP///////+9/" + ], + [ + 106, + 1, + "insert", + { + "characters": " str" + }, + "BAAAAC8BAAAAAAAAMAEAAAAAAAAAAAAAMAEAAAAAAAAxAQAAAAAAAAAAAAAxAQAAAAAAADIBAAAAAAAAAAAAADIBAAAAAAAAMwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALwEAAAAAAAAvAQAAAAAAAAAAAAAAAPC/" + ], + [ + 112, + 1, "right_delete", null, - "AQAAAC8BAAAAAAAALwEAAAAAAAACAAAALjU", - "AQAAAAAAAAABAAAALwEAAAAAAAAxAQAAAAAAAAAAAAAAAPC/" + "AQAAADUBAAAAAAAANQEAAAAAAADRAAAAQGRhdGFjbGFzcwpjbGFzcyBFbWFpbDoKICAgIGlkOiBpbnQKICAgIGVuY29kaW5nOiBzdHIKICAgIGRhdGU6IGRhdGV0aW1lCiAgICBmcm9tX2FkZHI6IHN0cgogICAgdG9fYWRkcjogc3RyCiAgICBzdWJqZWN0OiBzdHIKICAgIHBhcnRzOiBMaXN0W1BhcnRdCiAgICBhdHRhY2htZW50czogTGlzdFtBdHRhY2htZW50XQogICAgcGxhaW5fdGV4dF9jb250ZW50OiBzdHI", + "AQAAAAAAAAABAAAANQEAAAAAAAAGAgAAAAAAAAAAAAAAAAAA" + ], + [ + 113, + 1, + "left_delete", + null, + "AQAAADQBAAAAAAAANAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" + ], + [ + 120, + 1, + "insert", + { + "characters": "," + }, + "AQAAAEwAAAAAAAAATQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATAAAAAAAAABMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 121, + 1, + "insert", + { + "characters": " asd" + }, + "BAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 122, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"09.9999.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/model/comment.py\",\"position\":{\"line\":3,\"character\":38},\"symbolLabel\":\"asdict\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "asdict" + }, + "AgAAAE4AAAAAAAAATgAAAAAAAAADAAAAYXNkTgAAAAAAAABUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 128, + 2, + "left_delete", + null, + "AgAAAE0AAAAAAAAATQAAAAAAAAAHAAAAIGFzZGljdEwAAAAAAAAATAAAAAAAAAABAAAALA", + "AQAAAAAAAAABAAAATQAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" + ], + [ + 136, + 1, + "insert", + { + "characters": "\nid:" + }, + "BQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAjAAAAAAAAACNAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 137, + 1, + "insert", + { + "characters": " int" + }, + "BAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 138, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":7,\"sortText\":\"09.9999.int\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/model/comment.py\",\"position\":{\"line\":8,\"character\":11},\"symbolLabel\":\"int\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"int\"}}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "int" + }, + "AgAAAI4AAAAAAAAAjgAAAAAAAAADAAAAaW50jgAAAAAAAACRAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 147, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAABAAQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmZyb20gZGF0YWNsYXNzZXMgaW1wb3J0IGRhdGFjbGFzcwpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQoKQGRhdGFjbGFzcwpjbGFzcyBDb21tZW50OgogICAgaWQ6IGludAogICAgdXJsIDogc3RyCiAgICBjcmVhdGVkIDogZGF0ZXRpbWUKICAgIG5vdGlmaWVkIDogZGF0ZXRpbWUKICAgIHB1Ymxpc2hlZCA6IGRhdGV0aW1lCiAgICBhdXRob3JfbmFtZSA6IHN0cgogICAgYXV0aG9yX3NpdGUgOiBzdHIKICAgIGF1dGhvcl9ncmF2YXRhciA6IHN0cgogICAgY29udGVudCA6IHN0cgoAAAAAAAAAADkBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQAEAAAAAAABAAQAAAAAAAAAAAAAAAPC/" + ], + [ + 151, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" + ], + [ + 152, + 1, + "insert", + { + "characters": " 0" + }, + "AgAAAJQAAAAAAAAAlQAAAAAAAAAAAAAAlQAAAAAAAACWAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlAAAAAAAAACUAAAAAAAAAAAAAAAAAPC/" + ], + [ + 155, + 1, + "cut", + null, + "AQAAAIsAAAAAAAAAiwAAAAAAAAALAAAAaWQ6IGludCA9IDA", + "AQAAAAAAAAABAAAAlgAAAAAAAACLAAAAAAAAAAAAAAAAAEJA" + ], + [ + 157, + 1, + "left_delete", + null, + "AQAAAIYAAAAAAAAAhgAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAAAA" + ], + [ + 160, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAADABAAAAAAAAMQEAAAAAAAAAAAAAMQEAAAAAAAA1AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMAEAAAAAAAAwAQAAAAAAAAAAAAAAAPC/" + ], + [ + 161, + 1, + "paste", + null, + "AQAAADUBAAAAAAAAQAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" + ], + [ + 163, + 1, + "cut", + null, + "AQAAADUBAAAAAAAANQEAAAAAAAALAAAAaWQ6IGludCA9IDA", + "AQAAAAAAAAABAAAAQAEAAAAAAAA1AQAAAAAAAAAAAAAAAEJA" + ], + [ + 165, + 1, + "left_delete", + null, + "AQAAADABAAAAAAAAMAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAMQEAAAAAAAAxAQAAAAAAAAAAAAAAAAAA" + ], + [ + 167, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAGJA" + ], + [ + 168, + 1, + "paste", + null, + "AQAAAIkAAAAAAAAAlAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAPC/" + ], + [ + 170, + 1, + "insert", + { + "characters": "\t" + }, + "AQAAAIkAAAAAAAAAjQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAAAA" + ], + [ + 173, + 2, + "left_delete", + null, + "AgAAAIcAAAAAAAAAhwAAAAAAAAABAAAAIIYAAAAAAAAAhgAAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" + ], + [ + 175, + 1, + "insert", + { + "characters": " " + }, + "AgAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAgF9A" + ], + [ + 176, + 1, + "left_delete", + null, + "AQAAAKYAAAAAAAAApgAAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAPC/" + ], + [ + 177, + 1, + "insert", + { + "characters": "=" + }, + "AQAAAKYAAAAAAAAApwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApgAAAAAAAACmAAAAAAAAAAAAAAAAAPC/" + ], + [ + 178, + 1, + "insert", + { + "characters": " " + }, + "AQAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAPC/" + ], + [ + 179, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAKgAAAAAAAAAqgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqAAAAAAAAACoAAAAAAAAAAAAAAAAAPC/" + ], + [ + 183, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAMAAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAADCAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwAAAAAAAAADAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 184, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAMIAAAAAAAAAwwAAAAAAAAAAAAAAwwAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMUAAAAAAAAAAAAAAMUAAAAAAAAAxgAAAAAAAAAAAAAAxgAAAAAAAADHAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" + ], + [ + 186, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAN4AAAAAAAAA3wAAAAAAAAAAAAAA3wAAAAAAAADgAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAgG9A" + ], + [ + 187, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" + ], + [ + 189, + 1, + "trim_trailing_white_space", + null, + "AgAAAFEBAAAAAAAAUQEAAAAAAAAEAAAAICAgIJYAAAAAAAAAlgAAAAAAAAACAAAAICA", + "AQAAAAAAAAABAAAA5QAAAAAAAADlAAAAAAAAAAAAAAAAAPC/" + ], + [ + 201, + 1, + "insert", + { + "characters": "datetime." + }, + "CgAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAAAQAAABOb25lwgAAAAAAAADDAAAAAAAAAAAAAADDAAAAAAAAAMQAAAAAAAAAAAAAAMQAAAAAAAAAxQAAAAAAAAAAAAAAxQAAAAAAAADGAAAAAAAAAAAAAADGAAAAAAAAAMcAAAAAAAAAAAAAAMcAAAAAAAAAyAAAAAAAAAAAAAAAyAAAAAAAAADJAAAAAAAAAAAAAADJAAAAAAAAAMoAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQAAAAAAAADFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 202, + 1, + "insert", + { + "characters": "de" + }, + "AgAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADMAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" + ], + [ + 210, + 1, + "insert", + { + "characters": "ow" + }, + "AwAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADLAAAAAAAAAAIAAABkZcsAAAAAAAAAzAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAygAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 211, + 2, + "left_delete", + null, + "AgAAAMsAAAAAAAAAywAAAAAAAAABAAAAd8oAAAAAAAAAygAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 212, + 1, + "insert", + { + "characters": "now" + }, + "AwAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADMAAAAAAAAAAAAAADMAAAAAAAAAM0AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" + ], + [ + 213, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAM0AAAAAAAAAzwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzQAAAAAAAADNAAAAAAAAAAAAAAAAAPC/" + ], + [ + 221, + 1, + "paste", + null, + "AgAAAOkAAAAAAAAA9wAAAAAAAAAAAAAA9wAAAAAAAAD3AAAAAAAAAAQAAABOb25l", + "AQAAAAAAAAABAAAA7QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" + ], + [ + 223, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADwEAAAAAAAAPAQAAAAAAAAAAAAAA8HVA" + ], + [ + 224, + 1, + "insert", + { + "characters": " " + }, + "AQAAABEBAAAAAAAAEgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" + ], + [ + 225, + 1, + "paste", + null, + "AQAAABIBAAAAAAAAIAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" + ], + [ + 227, + 1, + "insert", + { + "characters": " =" + }, + "AgAAADUBAAAAAAAANgEAAAAAAAAAAAAANgEAAAAAAAA3AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAgHZA" + ], + [ + 228, + 1, + "insert", + { + "characters": " " + }, + "AQAAADcBAAAAAAAAOAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" + ], + [ + 229, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAADgBAAAAAAAAOgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" + ], + [ + 231, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAE8BAAAAAAAAUAEAAAAAAAAAAAAAUAEAAAAAAABRAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATwEAAAAAAABPAQAAAAAAAAAAAAAAAGtA" + ], + [ + 232, + 1, + "insert", + { + "characters": " " + }, + "AQAAAFEBAAAAAAAAUgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUQEAAAAAAABRAQAAAAAAAAAAAAAAAPC/" + ], + [ + 233, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAFIBAAAAAAAAVAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUgEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" + ], + [ + 237, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAG0BAAAAAAAAbgEAAAAAAAAAAAAAbgEAAAAAAABvAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbQEAAAAAAABtAQAAAAAAAAAAAAAAAPC/" + ], + [ + 238, + 1, + "insert", + { + "characters": " " + }, + "AQAAAG8BAAAAAAAAcAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbwEAAAAAAABvAQAAAAAAAAAAAAAAAPC/" + ], + [ + 239, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAHABAAAAAAAAcgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcAEAAAAAAABwAQAAAAAAAAAAAAAAAPC/" + ], + [ + 241, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAIMBAAAAAAAAhAEAAAAAAAAAAAAAhAEAAAAAAACFAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgwEAAAAAAACDAQAAAAAAAAAAAAAAgG9A" + ], + [ + 242, + 1, + "insert", + { + "characters": " " + }, + "AQAAAIUBAAAAAAAAhgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQEAAAAAAACFAQAAAAAAAAAAAAAAAPC/" + ], + [ + 243, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAIYBAAAAAAAAiAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" + ], + [ + 249, + 1, + "insert", + { + "characters": "None" + }, + "BQAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAAA4AAABkYXRldGltZS5ub3coKcIAAAAAAAAAwwAAAAAAAAAAAAAAwwAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMUAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQAAAAAAAADPAAAAAAAAAAAAAAAAAPC/" + ], + [ + 258, + 1, + "insert", + { + "characters": "Optional(" + }, + "CQAAALYAAAAAAAAAtwAAAAAAAAAAAAAAtwAAAAAAAAC4AAAAAAAAAAAAAAC4AAAAAAAAALkAAAAAAAAAAAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAAvQAAAAAAAAC+AAAAAAAAAAAAAAC+AAAAAAAAAL8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtgAAAAAAAAC2AAAAAAAAAAAAAAAAAPC/" + ], + [ + 259, + 1, + "left_delete", + null, + "AQAAAL4AAAAAAAAAvgAAAAAAAAABAAAAKA", + "AQAAAAAAAAABAAAAvwAAAAAAAAC/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 260, + 1, + "insert", + { + "characters": "[" + }, + "AQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgAAAAAAAAC+AAAAAAAAAAAAAAAAAPC/" + ], + [ + 262, + 1, + "insert", + { + "characters": "]" + }, + "AQAAAMcAAAAAAAAAyAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxwAAAAAAAADHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 267, + 1, + "", + null, + "AQAAAGsAAAAAAAAAhgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 278, + 1, + "paste", + null, + "AgAAAPkAAAAAAAAACwEAAAAAAAAAAAAACwEAAAAAAAALAQAAAAAAAAgAAABkYXRldGltZQ", + "AQAAAAAAAAABAAAA+QAAAAAAAAABAQAAAAAAAAAAAAAAAPC/" + ], + [ + 283, + 1, + "paste", + null, + "AgAAACwBAAAAAAAAPgEAAAAAAAAAAAAAPgEAAAAAAAA+AQAAAAAAAAgAAABkYXRldGltZQ", + "AQAAAAAAAAABAAAALAEAAAAAAAA0AQAAAAAAAAAAAAAAAPC/" + ], + [ + 287, + 1, + "insert", + { + "characters": "None" + }, + "BQAAAA4BAAAAAAAADwEAAAAAAAAAAAAADwEAAAAAAAAPAQAAAAAAAA4AAABkYXRldGltZS5ub3coKQ8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAARAQAAAAAAABIBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADgEAAAAAAAAcAQAAAAAAAP///////+9/" + ], + [ + 291, + 1, + "insert", + { + "characters": "NOne" + }, + "BQAAADcBAAAAAAAAOAEAAAAAAAAAAAAAOAEAAAAAAAA4AQAAAAAAAA4AAABkYXRldGltZS5ub3coKTgBAAAAAAAAOQEAAAAAAAAAAAAAOQEAAAAAAAA6AQAAAAAAAAAAAAA6AQAAAAAAADsBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANwEAAAAAAABFAQAAAAAAAP///////+9/" + ], + [ + 294, + 1, + "insert", + { + "characters": "i" + }, + "AgAAADgBAAAAAAAAOQEAAAAAAAAAAAAAOQEAAAAAAAA5AQAAAAAAAAEAAABP", + "AQAAAAAAAAABAAAAOQEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" + ], + [ + 295, + 1, + "left_delete", + null, + "AQAAADgBAAAAAAAAOAEAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" + ], + [ + 296, + 1, + "insert", + { + "characters": "o" + }, + "AQAAADgBAAAAAAAAOQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" + ], + [ + 300, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAACkAQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmZyb20gZGF0YWNsYXNzZXMgaW1wb3J0IGRhdGFjbGFzcwpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQpmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwKCkBkYXRhY2xhc3MKY2xhc3MgQ29tbWVudDoKICAgIGlkOiBpbnQgPSAwCiAgICB1cmw6IHN0ciA9ICIiCiAgICBjcmVhdGVkOiBPcHRpb25hbFtkYXRldGltZV0gPSBOb25lCiAgICBub3RpZmllZDogT3B0aW9uYWxbZGF0ZXRpbWVdID0gTm9uZQogICAgcHVibGlzaGVkOiBPcHRpb25hbFtkYXRldGltZV0gPSBOb25lCiAgICBhdXRob3JfbmFtZTogc3RyID0gIiIKICAgIGF1dGhvcl9zaXRlOiBzdHIgPSAiIgogICAgYXV0aG9yX2dyYXZhdGFyOiBzdHIgPSAiIgogICAgY29udGVudDogc3RyID0gIiIKAAAAAAAAAAClAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVQEAAAAAAABVAQAAAAAAAAAAAAAAYHVA" + ] + ] + }, + { + "file": "tests/test_api.py", + "settings": + { + "buffer_size": 1655, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "insert", + { + "characters": "configure" + }, + "CgAAAFkCAAAAAAAAWgIAAAAAAAAAAAAAWgIAAAAAAABaAgAAAAAAAAUAAABzZXR1cFoCAAAAAAAAWwIAAAAAAAAAAAAAWwIAAAAAAABcAgAAAAAAAAAAAABcAgAAAAAAAF0CAAAAAAAAAAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAGACAAAAAAAAYQIAAAAAAAAAAAAAYQIAAAAAAABiAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAWQIAAAAAAABeAgAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "insert", + { + "characters": "sqlite://" + }, + "CQAAAGQCAAAAAAAAZQIAAAAAAAAAAAAAZQIAAAAAAABmAgAAAAAAAAAAAABmAgAAAAAAAGcCAAAAAAAAAAAAAGcCAAAAAAAAaAIAAAAAAAAAAAAAaAIAAAAAAABpAgAAAAAAAAAAAABpAgAAAAAAAGoCAAAAAAAAAAAAAGoCAAAAAAAAawIAAAAAAAAAAAAAawIAAAAAAABsAgAAAAAAAAAAAABsAgAAAAAAAG0CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZAIAAAAAAABkAgAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert", + { + "characters": "memory" + }, + "BgAAAGsCAAAAAAAAbAIAAAAAAAAAAAAAbAIAAAAAAABtAgAAAAAAAAAAAABtAgAAAAAAAG4CAAAAAAAAAAAAAG4CAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABwAgAAAAAAAAAAAABwAgAAAAAAAHECAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAawIAAAAAAABrAgAAAAAAAAAAAAAAAPC/" + ], + [ + 15, + 1, + "insert", + { + "characters": "dummy." + }, + "BwAAAHMCAAAAAAAAdAIAAAAAAAAAAAAAdAIAAAAAAAB0AgAAAAAAAAgAAAA6bWVtb3J5OnQCAAAAAAAAdQIAAAAAAAAAAAAAdQIAAAAAAAB2AgAAAAAAAAAAAAB2AgAAAAAAAHcCAAAAAAAAAAAAAHcCAAAAAAAAeAIAAAAAAAAAAAAAeAIAAAAAAAB5AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcwIAAAAAAAB7AgAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 1, + "insert", + { + "characters": "db" + }, + "AgAAAHkCAAAAAAAAegIAAAAAAAAAAAAAegIAAAAAAAB7AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAeQIAAAAAAAB5AgAAAAAAAAAAAAAAAPC/" + ], + [ + 19, + 1, + "insert", + { + "characters": "db" + }, + "AwAAAHMCAAAAAAAAdAIAAAAAAAAAAAAAdAIAAAAAAAB0AgAAAAAAAAUAAABkdW1teXQCAAAAAAAAdQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAeAIAAAAAAABzAgAAAAAAAAAAAAAAAPC/" + ], + [ + 22, + 1, + "insert", + { + "characters": "sqlite" + }, + "BwAAAHYCAAAAAAAAdwIAAAAAAAAAAAAAdwIAAAAAAAB3AgAAAAAAAAIAAABkYncCAAAAAAAAeAIAAAAAAAAAAAAAeAIAAAAAAAB5AgAAAAAAAAAAAAB5AgAAAAAAAHoCAAAAAAAAAAAAAHoCAAAAAAAAewIAAAAAAAAAAAAAewIAAAAAAAB8AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgIAAAAAAAB4AgAAAAAAAAAAAAAAAPC/" + ], + [ + 31, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAHECAAAAAAAAcgIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" + ], + [ + 71, + 1, + "toggle_breakpoint", + null, + "AQAAAL0BAAAAAAAA9wEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" + ], + [ + 74, + 1, + "toggle_breakpoint", + null, + "AQAAAL0BAAAAAAAAvQEAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDU1ZGNmYmI4IC8vCg", + "AQAAAAAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "tests/test_db.py", + "settings": + { + "buffer_size": 1835, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 6, + 1, + "insert", + { + "characters": "configure" + }, + "CgAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACmAAAAAAAAAAUAAABzZXR1cKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApQAAAAAAAACqAAAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert", + { + "characters": "sqlite" + }, + "BgAAALAAAAAAAAAAsQAAAAAAAAAAAAAAsQAAAAAAAACyAAAAAAAAAAAAAACyAAAAAAAAALMAAAAAAAAAAAAAALMAAAAAAAAAtAAAAAAAAAAAAAAAtAAAAAAAAAC1AAAAAAAAAAAAAAC1AAAAAAAAALYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAPC/" + ], + [ + 14, + 1, + "insert", + { + "characters": "//db." + }, + "BQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAMEAAAAAAAAAAAAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADDAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgAAAAAAAAC+AAAAAAAAAAAAAAAAAPC/" + ], + [ + 15, + 1, + "insert", + { + "characters": "sqlite" + }, + "BgAAAMMAAAAAAAAAxAAAAAAAAAAAAAAAxAAAAAAAAADFAAAAAAAAAAAAAADFAAAAAAAAAMYAAAAAAAAAAAAAAMYAAAAAAAAAxwAAAAAAAAAAAAAAxwAAAAAAAADIAAAAAAAAAAAAAADIAAAAAAAAAMkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwwAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 17, + 1, + "trim_trailing_white_space", + null, + "AQAAAMsAAAAAAAAAywAAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAyQAAAAAAAADJAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "tests/test_form.py", + "settings": + { + "buffer_size": 1125, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 6, + 1, + "insert", + { + "characters": "configure" + }, + "CgAAAAgBAAAAAAAACQEAAAAAAAAAAAAACQEAAAAAAAAJAQAAAAAAAAUAAABzZXR1cAkBAAAAAAAACgEAAAAAAAAAAAAACgEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACAEAAAAAAAANAQAAAAAAAAAAAAAAAPC/" + ], + [ + 8, + 1, + "insert", + { + "characters": "q" + }, + "AQAAABMBAAAAAAAAFAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" + ], + [ + 9, + 1, + "left_delete", + null, + "AQAAABMBAAAAAAAAEwEAAAAAAAABAAAAcQ", + "AQAAAAAAAAABAAAAFAEAAAAAAAAUAQAAAAAAAAAAAAAAAPC/" + ], + [ + 10, + 1, + "insert", + { + "characters": "sqlite" + }, + "BgAAABMBAAAAAAAAFAEAAAAAAAAAAAAAFAEAAAAAAAAVAQAAAAAAAAAAAAAVAQAAAAAAABYBAAAAAAAAAAAAABYBAAAAAAAAFwEAAAAAAAAAAAAAFwEAAAAAAAAYAQAAAAAAAAAAAAAYAQAAAAAAABkBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" + ], + [ + 13, + 1, + "insert", + { + "characters": "//db.sqli" + }, + "CQAAACEBAAAAAAAAIgEAAAAAAAAAAAAAIgEAAAAAAAAjAQAAAAAAAAAAAAAjAQAAAAAAACQBAAAAAAAAAAAAACQBAAAAAAAAJQEAAAAAAAAAAAAAJQEAAAAAAAAmAQAAAAAAAAAAAAAmAQAAAAAAACcBAAAAAAAAAAAAACcBAAAAAAAAKAEAAAAAAAAAAAAAKAEAAAAAAAApAQAAAAAAAAAAAAApAQAAAAAAACoBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIQEAAAAAAAAhAQAAAAAAAAAAAAAAAPC/" + ], + [ + 14, + 1, + "insert", + { + "characters": "te" + }, + "AgAAACoBAAAAAAAAKwEAAAAAAAAAAAAAKwEAAAAAAAAsAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKgEAAAAAAAAqAQAAAAAAAAAAAAAAAPC/" + ], + [ + 16, + 1, + "trim_trailing_white_space", + null, + "AQAAAC4BAAAAAAAALgEAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAALAEAAAAAAAAsAQAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "config.ini", + "settings": + { + "buffer_size": 500, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 3, + 1, + "paste", + null, + "AQAAAEYAAAAAAAAAXQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABGAAAAAAAAAEYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 4, + 1, + "insert", + { + "characters": "\n" + }, + "AQAAAF0AAAAAAAAAXgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + ], + [ + 4, + 1, + "right_delete", + null, + "AQAAACsAAAAAAAAAKwAAAAAAAAAaAAAAZGJfc3FsaXRlX2ZpbGUgPSBkYi5zcWxpdGU", + "AQAAAAAAAAABAAAARQAAAAAAAAArAAAAAAAAAAAAAAAAAAAA" + ], + [ + 5, + 1, + "left_delete", + null, + "AQAAACoAAAAAAAAAKgAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAKwAAAAAAAAArAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "stacosys/service/configuration.py", + "settings": + { + "buffer_size": 2563, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 6, + 1, + "insert", + { + "characters": "\nDB" + }, + "BAAAAHcAAAAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAB8AAAAAAAAAAAAAAB8AAAAAAAAAH0AAAAAAAAAAAAAAH0AAAAAAAAAfgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB3AAAAAAAAAHcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 7, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB+AAAAAAAAAH4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 8, + 1, + "insert", + { + "characters": " " + }, + "AQAAAIAAAAAAAAAAgQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 9, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAAIEAAAAAAAAAgwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 10, + 1, + "insert", + { + "characters": "main.db" + }, + "BwAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAAAAAAAAAIIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 8, + 1, + "right_delete", + null, + "AQAAAI8AAAAAAAAAjwAAAAAAAAAmAAAAREJfU1FMSVRFX0ZJTEUgPSAibWFpbi5kYl9zcWxpdGVfZmlsZSI", + "AQAAAAAAAAABAAAAtQAAAAAAAACPAAAAAAAAAAAAAAAAAEJA" + ], + [ + 10, + 1, + "left_delete", + null, + "AQAAAIoAAAAAAAAAigAAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAAAA" + ], + [ + 12, + 1, + "trim_trailing_white_space", + null, + "AQAAAIoAAAAAAAAAigAAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "tests/test_config.py", + "settings": + { + "buffer_size": 1113, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 3, + 1, + "insert", + { + "characters": "sqlite:/" + }, + "CAAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC3AAAAAAAAAAAAAAC3AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAAC9AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtQAAAAAAAAC1AAAAAAAAAAAAAAAAAPC/" + ], + [ + 4, + 1, + "insert", + { + "characters": "/" + }, + "AQAAAL0AAAAAAAAAvgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvQAAAAAAAAC9AAAAAAAAAAAAAAAAAPC/" + ], + [ + 12, + 1, + "right_delete", + null, + "AQAAAD4BAAAAAAAAPgEAAAAAAAAMAAAAX1NRTElURV9GSUxF", + "AQAAAAAAAAABAAAAPgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" + ], + [ + 20, + 1, + "right_delete", + null, + "AQAAAOUBAAAAAAAA5QEAAAAAAAAMAAAAX1NRTElURV9GSUxF", + "AQAAAAAAAAABAAAA5QEAAAAAAADxAQAAAAAAAAAAAAAAAPC/" + ], + [ + 24, + 1, + "right_delete", + null, + "AQAAACsCAAAAAAAAKwIAAAAAAAAMAAAAX1NRTElURV9GSUxF", + "AQAAAAAAAAABAAAAKwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + ], + [ + 31, + 1, + "right_delete", + null, + "AQAAAGgDAAAAAAAAaAMAAAAAAAAMAAAAX1NRTElURV9GSUxF", + "AQAAAAAAAAABAAAAaAMAAAAAAAB0AwAAAAAAAAAAAAAAAPC/" ] ] } @@ -140,6 +8316,22 @@ "last_filter": "", "selected_items": [ + [ + "break", + "Python Breakpoints: Toggle" + ], + [ + "togg", + "Python Breakpoints: Toggle" + ], + [ + "diag", + "LSP: Toggle Diagnostics Panel" + ], + [ + "lsp", + "LSP: Toggle Diagnostics Panel" + ], [ "comment", "Toggle Comment" @@ -148,10 +8340,6 @@ "install", "Package Control: Install Package" ], - [ - "lsp", - "LSP: Toggle Diagnostics Panel" - ], [ "rename", "LSP: Rename" @@ -168,10 +8356,6 @@ "move", "File: Move…" ], - [ - "break", - "Python Breakpoints: Clear All" - ], [ "remove", "Package Control: Remove Package" @@ -188,10 +8372,6 @@ "insta", "Package Control: Install Package" ], - [ - "diag", - "LSP: Toggle Diagnostics Panel" - ], [ "docstr", "AutoDocstring: Current" @@ -266,8 +8446,7 @@ "expanded_folders": [ "/home/yannic/work/stacosys", - "/home/yannic/work/stacosys/.github", - "/home/yannic/work/stacosys/.github/workflows", + "/home/yannic/work/stacosys/dbmigration", "/home/yannic/work/stacosys/docker", "/home/yannic/work/stacosys/stacosys", "/home/yannic/work/stacosys/stacosys/db", @@ -279,22 +8458,33 @@ ], "file_history": [ - "/home/yannic/work/stacosys/stacosys/interface/form.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", - "/home/yannic/work/stacosys/config-dev.ini", + "/home/yannic/work/stacosys/dbmigration/migrate_from_1.1_to_2.0.py", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/pydal/objects.py", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/markdown/extensions/def_list.py", + "/home/yannic/work/stacosys/stacosys/db/database.py", + "/home/yannic/work/stacosys/pyproject.toml", + "/home/yannic/work/stacosys/tests/test_rssfeed.py", + "/home/yannic/work/stacosys/tests/test_db.py", + "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", "/home/yannic/work/stacosys/stacosys/service/mail.py", + "/home/yannic/work/stacosys/stacosys/interface/form.py", + "/home/yannic/work/stacosys/stacosys/interface/__init__.py", + "/home/yannic/work/stacosys/stacosys/interface/api.py", + "/home/yannic/work/stacosys/stacosys/db/dao.py", + "/home/yannic/work/stacosys/stacosys/model/comment.py", + "/home/yannic/work/stacosys/stacosys/run.py", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/peewee.py", + "/home/yannic/work/stacosys/config-dev.ini", + "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", "/home/yannic/work/stacosys/tests/test_mail.py", "/home/yannic/work/stacosys/Makefile", - "/home/yannic/work/stacosys/tests/test_db.py", "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", - "/home/yannic/work/stacosys/stacosys/run.py", "/home/yannic/work/stacosys/tests/test_config.py", "/home/yannic/work/stacosys/.venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi", "/usr/lib64/python3.11/smtplib.py", "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", "/home/yannic/work/stacosys/stacosys/service/configuration.py", "/home/yannic/work/stacosys/config.ini", - "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", "/home/yannic/work/stacosys/comments.xml", "/home/yannic/work/stacosys/tests/test_api.py", "/home/yannic/work/stacosys/stacosys/service/__init__.py", @@ -309,13 +8499,8 @@ "/home/yannic/work/stacosys/stacosys/core/mailer.py", "/home/yannic/work/stacosys/stacosys/conf/config.py", "/home/yannic/work/stacosys/stacosys/core/rss.py", - "/home/yannic/work/stacosys/stacosys/db/dao.py", - "/home/yannic/work/stacosys/stacosys/db/database.py", "/home/yannic/work/stacosys/run.sh", - "/home/yannic/work/stacosys/stacosys/interface/__init__.py", - "/home/yannic/work/stacosys/stacosys/interface/api.py", "/home/yannic/work/stacosys/stacosys.sublime-project", - "/home/yannic/work/stacosys/stacosys/model/comment.py", "/home/yannic/work/stacosys/pylintrc", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/flask/app.py", "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/sys.pyi", @@ -339,6 +8524,10 @@ "case_sensitive": false, "find_history": [ + "asdict", + "def delete", + "db_dal", + "tox", "apscheduler", "_lang", "config", @@ -369,26 +8558,47 @@ [ { "buffer": 0, - "file": "pyproject.toml", + "file": "stacosys/db/__init__.py", "selected": true, "semi_transient": false, "settings": { - "buffer_size": 620, + "buffer_size": 672, "regions": { }, "selection": [ [ - 481, - 481 + 195, + 195 ] ], "settings": { - "lsp_uri": "file:///home/yannic/work/stacosys/pyproject.toml", - "syntax": "Packages/Text/Plain text.tmLanguage" + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 3, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/__init__.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true }, "translation.x": 0.0, "translation.y": 0.0, @@ -397,6 +8607,576 @@ "stack_index": 0, "stack_multiselect": false, "type": "text" + }, + { + "buffer": 1, + "file": "stacosys/run.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2472, + "regions": + { + }, + "selection": + [ + [ + 120, + 120 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 6, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 2, + "file": "config-dev.ini", + "semi_transient": false, + "settings": + { + "buffer_size": 657, + "regions": + { + }, + "selection": + [ + [ + 59, + 59 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/config-dev.ini", + "syntax": "Packages/Text/Plain text.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 1, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 3, + "file": "dbmigration/migrate_from_3.3_to_4.0.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1022, + "regions": + { + }, + "selection": + [ + [ + 369, + 369 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/dbmigration/migrate_from_3.3_to_4.0.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 2, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 4, + "file": "stacosys/db/dao.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2026, + "regions": + { + }, + "selection": + [ + [ + 80, + 80 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/dao.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 7, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 5, + "file": "stacosys/service/rssfeed.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1754, + "regions": + { + }, + "selection": + [ + [ + 1668, + 1631 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/rssfeed.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 456.0, + "zoom_level": 1.0 + }, + "stack_index": 8, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 6, + "file": "stacosys/model/comment.py", + "semi_transient": false, + "settings": + { + "buffer_size": 421, + "regions": + { + }, + "selection": + [ + [ + 421, + 421 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/model/comment.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 11, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 7, + "file": "tests/test_api.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1655, + "regions": + { + }, + "selection": + [ + [ + 935, + 935 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 9, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 8, + "file": "tests/test_db.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1835, + "regions": + { + }, + "selection": + [ + [ + 335, + 335 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_db.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 10, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 9, + "file": "tests/test_form.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1125, + "regions": + { + }, + "selection": + [ + [ + 300, + 300 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_form.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 12, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 10, + "file": "config.ini", + "semi_transient": false, + "settings": + { + "buffer_size": 500, + "regions": + { + }, + "selection": + [ + [ + 66, + 66 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/config.ini", + "syntax": "Packages/Text/Plain text.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 5, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 11, + "file": "stacosys/service/configuration.py", + "semi_transient": false, + "settings": + { + "buffer_size": 2563, + "regions": + { + }, + "selection": + [ + [ + 138, + 138 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 3, + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/configuration.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 4, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 12, + "file": "tests/test_config.py", + "semi_transient": false, + "settings": + { + "buffer_size": 1113, + "regions": + { + }, + "selection": + [ + [ + 1113, + 1113 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_config.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 171.0, + "zoom_level": 1.0 + }, + "stack_index": 3, + "stack_multiselect": false, + "type": "text" } ] } @@ -473,6 +9253,10 @@ "last_filter": "", "selected_items": [ + [ + "def del", + ".venv/lib/python3.11/site-packages/markdown/extensions/def_list.py" + ], [ "socket.p", ".venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi" diff --git a/stacosys/db/__init__.py b/stacosys/db/__init__.py index e69de29..dcd1ef8 100644 --- a/stacosys/db/__init__.py +++ b/stacosys/db/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pydal import DAL, Field + + +class Database: + + db_dal = DAL() + + def configure(self, db_uri): + self.db_dal = DAL(db_uri, migrate=False) + self.db_dal.define_table( + "comment", + Field("url"), + Field("created", type="datetime"), + Field("notified", type="datetime"), + Field("published", type="datetime"), + Field("author_name"), + Field("author_site"), + Field("author_gravatar"), + Field("content", type="text"), + ) + + def get(self): + return self.db_dal + + +database = Database() +db = database.get diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index fcd3689..7d9abdb 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -1,67 +1,80 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +# pylint: disable=singleton-comparison + from datetime import datetime +from stacosys.db import db from stacosys.model.comment import Comment -TIME_FORMAT = "%Y-%m-%d %H:%M:%S" - def find_comment_by_id(comment_id): - return Comment.get_by_id(comment_id) + return db().comment(comment_id) def notify_comment(comment: Comment): - comment.notified = datetime.now().strftime(TIME_FORMAT) - comment.save() + db()(db().comment.id == comment.id).update(notified=datetime.now()) + db().commit() def publish_comment(comment: Comment): - comment.published = datetime.now().strftime(TIME_FORMAT) - comment.save() + db()(db().comment.id == comment.id).update(published=datetime.now()) + db().commit() def delete_comment(comment: Comment): - comment.delete_instance() + db()(db().comment.id == comment.id).delete() + db().commit() def find_not_notified_comments(): - return Comment.select().where(Comment.notified.is_null()) + return db()(db().comment.notified == None).select() def find_not_published_comments(): - return Comment.select().where(Comment.published.is_null()) + return db()(db().comment.published == None).select() def find_published_comments_by_url(url): - return ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .order_by(+Comment.published) + return db()((db().comment.url == url) & (db().comment.published != None)).select( + orderby=db().comment.published ) def count_published_comments(url): return ( - Comment.select(Comment) - .where((Comment.url == url) & (Comment.published.is_null(False))) - .count() + db()((db().comment.url == url) & (db().comment.published != None)).count() if url - else Comment.select(Comment).where(Comment.published.is_null(False)).count() + else db()(db().comment.published != None).count() + ) + + +def find_recent_published_comments(): + return db()(db().comment.published != None).select( + orderby=~db().comment.published, limitby=(0, 10) ) def create_comment(url, author_name, author_site, author_gravatar, message): - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment = Comment( + row = db().comment.insert( url=url, author_name=author_name, author_site=author_site, author_gravatar=author_gravatar, content=message, - created=created, + created=datetime.now(), notified=None, published=None, ) - comment.save() - return comment + db().commit() + return Comment( + id=row.id, + url=row.url, + author_name=row.author_name, + author_site=row.author_site, + author_gravatar=row.author_gravatar, + content=row.content, + created=row.created, + notified=row.notified, + published=row.published, + ) diff --git a/stacosys/db/database.py b/stacosys/db/database.py deleted file mode 100644 index 08a7580..0000000 --- a/stacosys/db/database.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- -# pylint: disable=import-outside-toplevel - -from peewee import Model -from playhouse.db_url import SqliteDatabase - -db = SqliteDatabase(None) - - -class BaseModel(Model): - class Meta: - database = db - - -def setup(db_url): - db.init(db_url) - db.connect() - - from stacosys.model.comment import Comment - - db.create_tables([Comment], safe=True) - - -def get_db(): - return db diff --git a/stacosys/model/comment.py b/stacosys/model/comment.py index a6f3324..41688a5 100644 --- a/stacosys/model/comment.py +++ b/stacosys/model/comment.py @@ -1,17 +1,19 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -from peewee import CharField, DateTimeField, TextField - -from stacosys.db.database import BaseModel +from dataclasses import dataclass +from datetime import datetime +from typing import Optional -class Comment(BaseModel): - url = CharField() - created = DateTimeField() - notified = DateTimeField(null=True, default=None) - published = DateTimeField(null=True, default=None) - author_name = CharField() - author_site = CharField(default="") - author_gravatar = CharField(default="") - content = TextField() +@dataclass +class Comment: + id: int = 0 + url: str = "" + created: Optional[datetime] = None + notified: Optional[datetime] = None + published: Optional[datetime] = None + author_name: str = "" + author_site: str = "" + author_gravatar: str = "" + content: str = "" diff --git a/stacosys/run.py b/stacosys/run.py index 8bd26ad..9cf26cd 100644 --- a/stacosys/run.py +++ b/stacosys/run.py @@ -43,14 +43,8 @@ def stacosys_server(config_pathname): sys.exit(1) logger.info(config) - # check database file exists (prevents from creating a fresh db) - db_pathname = config.get(ConfigParameter.DB_SQLITE_FILE) - if not db_pathname or not os.path.isfile(db_pathname): - logger.error("Database file '%s' not found.", db_pathname) - sys.exit(1) - # initialize database - database.setup(db_pathname) + database.configure(config.get(ConfigParameter.DB)) logger.info("Start Stacosys application") diff --git a/stacosys/service/configuration.py b/stacosys/service/configuration.py index 85b3db8..d21dc87 100644 --- a/stacosys/service/configuration.py +++ b/stacosys/service/configuration.py @@ -6,7 +6,7 @@ from enum import Enum class ConfigParameter(Enum): - DB_SQLITE_FILE = "main.db_sqlite_file" + DB = "main.db" LANG = "main.lang" HTTP_HOST = "http.host" diff --git a/stacosys/service/rssfeed.py b/stacosys/service/rssfeed.py index 3e81389..80461a1 100644 --- a/stacosys/service/rssfeed.py +++ b/stacosys/service/rssfeed.py @@ -6,7 +6,7 @@ from datetime import datetime import markdown import PyRSS2Gen -from stacosys.model.comment import Comment +from stacosys.db import dao class Rss: @@ -32,12 +32,7 @@ class Rss: markdownizer = markdown.Markdown() items = [] - for row in ( - Comment.select() - .where(Comment.published) - .order_by(-Comment.published) - .limit(10) - ): + for row in dao.find_recent_published_comments(): item_link = f"{self._site_proto}://{self._site_url}{row.url}" items.append( PyRSS2Gen.RSSItem( diff --git a/tests/test_api.py b/tests/test_api.py index 8efd052..0d84a0c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -23,7 +23,7 @@ def init_test_db(): @pytest.fixture def client(): logger = logging.getLogger(__name__) - database.setup(":memory:") + database.configure("sqlite:memory://db.sqlite") init_test_db() logger.info(f"start interface {api}") return app.test_client() diff --git a/tests/test_config.py b/tests/test_config.py index db884c8..fe44ef2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,26 +6,26 @@ import pytest from stacosys.service import config from stacosys.service.configuration import ConfigParameter -EXPECTED_DB_SQLITE_FILE = "db.sqlite" +EXPECTED_DB_SQLITE_FILE = "sqlite://db.sqlite" EXPECTED_HTTP_PORT = 8080 EXPECTED_LANG = "fr" @pytest.fixture def init_config(): - config.put(ConfigParameter.DB_SQLITE_FILE, EXPECTED_DB_SQLITE_FILE) + config.put(ConfigParameter.DB, EXPECTED_DB_SQLITE_FILE) config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) def test_exists(init_config): - assert config.exists(ConfigParameter.DB_SQLITE_FILE) + assert config.exists(ConfigParameter.DB) def test_get(init_config): - assert config.get(ConfigParameter.DB_SQLITE_FILE) == EXPECTED_DB_SQLITE_FILE + assert config.get(ConfigParameter.DB) == EXPECTED_DB_SQLITE_FILE assert config.get(ConfigParameter.HTTP_HOST) == "" assert config.get(ConfigParameter.HTTP_PORT) == str(EXPECTED_HTTP_PORT) assert config.get_int(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT with pytest.raises(AssertionError): - config.get_bool(ConfigParameter.DB_SQLITE_FILE) + config.get_bool(ConfigParameter.DB) def test_put(init_config): assert not config.exists(ConfigParameter.LANG) diff --git a/tests/test_db.py b/tests/test_db.py index 649c5e7..d53133c 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -8,7 +8,7 @@ from stacosys.db import database @pytest.fixture def setup_db(): - database.setup(":memory:") + database.configure("sqlite:memory://db.sqlite") def test_dao_published(setup_db): diff --git a/tests/test_form.py b/tests/test_form.py index dedef3a..1ddd4ae 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -13,7 +13,7 @@ from stacosys.interface import form @pytest.fixture def client(): logger = logging.getLogger(__name__) - database.setup(":memory:") + database.configure("sqlite:memory://db.sqlite") logger.info(f"start interface {form}") return app.test_client() From 5cd86a42bf769ad1c1413a7cb7235bbcd6e52f1a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 1 Dec 2022 20:41:02 +0100 Subject: [PATCH 490/586] DB migration for test case --- stacosys/db/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys/db/__init__.py b/stacosys/db/__init__.py index dcd1ef8..01b2a36 100644 --- a/stacosys/db/__init__.py +++ b/stacosys/db/__init__.py @@ -9,7 +9,7 @@ class Database: db_dal = DAL() def configure(self, db_uri): - self.db_dal = DAL(db_uri, migrate=False) + self.db_dal = DAL(db_uri, migrate=db_uri.startswith("sqlite:memory")) self.db_dal.define_table( "comment", Field("url"), From 172039d7ab2c7fe87112a5fddb18b60ccf96529c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 2 Dec 2022 08:58:44 +0100 Subject: [PATCH 491/586] format tests --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2eb9197..6c11464 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ all: black test typehint lint black: - poetry run isort --multi-line 3 --profile black stacosys/ - poetry run black stacosys/ + poetry run isort --multi-line 3 --profile black stacosys/ tests/ + poetry run black --target-version py311 stacosys/ tests/ test: poetry run coverage run -m --source=stacosys pytest From 5ef4acf396374df35ef395382fd67feec18bf8d8 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 2 Dec 2022 08:59:09 +0100 Subject: [PATCH 492/586] reinstall black formatter --- poetry.lock | 75 +- pyproject.toml | 3 +- stacosys.sublime-workspace | 8363 +++++++++++++----------------------- 3 files changed, 2923 insertions(+), 5518 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2bb08e3..fc0d83d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -136,35 +136,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "flake8" -version = "6.0.0" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.8.1" - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.10.0,<2.11.0" -pyflakes = ">=3.0.0,<3.1.0" - -[[package]] -name = "flake8-black" -version = "0.3.5" -description = "flake8 plugin to call black as a code style validator" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -black = ">=22.1.0" -flake8 = ">=3" -tomli = "*" - -[package.extras] -develop = ["build", "twine"] - [[package]] name = "flask" version = "2.2.2" @@ -339,14 +310,6 @@ python-versions = ">=3.6" dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pycodestyle" -version = "2.10.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=3.6" - [[package]] name = "pydal" version = "20221110.1" @@ -355,14 +318,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "pyflakes" -version = "3.0.1" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=3.6" - [[package]] name = "pylint" version = "2.15.7" @@ -454,14 +409,6 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - [[package]] name = "tomlkit" version = "0.11.6" @@ -524,7 +471,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "~3.11" -content-hash = "eae5d8539c8fd2e80b005124c5439a61310128f7427bdc20affa92dec85085ee" +content-hash = "7ee7b17fa42c245160e4e376453b3d6cea354551dd40342ee57d59b6d71ea31d" [metadata.files] astroid = [ @@ -641,14 +588,6 @@ dill = [ docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] -flake8 = [ - {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, - {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, -] -flake8-black = [ - {file = "flake8-black-0.3.5.tar.gz", hash = "sha256:9e93252b1314a8eb3c2f55dec54a07239e502b12f57567f2c105f2202714b15e"}, - {file = "flake8_black-0.3.5-py3-none-any.whl", hash = "sha256:4948a579fdddd98fbf935fd94255dfcfce560c4ddc1ceee08e3f12d6114c8619"}, -] flask = [ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, @@ -796,17 +735,9 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pycodestyle = [ - {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, - {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, -] pydal = [ {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, ] -pyflakes = [ - {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, - {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, -] pylint = [ {file = "pylint-2.15.7-py3-none-any.whl", hash = "sha256:1d561d1d3e8be9dd880edc685162fbdaa0409c88b9b7400873c0cf345602e326"}, {file = "pylint-2.15.7.tar.gz", hash = "sha256:91e4776dbcb4b4d921a3e4b6fec669551107ba11f29d9199154a01622e460a57"}, @@ -830,10 +761,6 @@ requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] tomlkit = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, diff --git a/pyproject.toml b/pyproject.toml index 3eff290..f8e7c83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,6 @@ version = "3.3" description = "STAtic COmmenting SYStem" authors = ["Yax"] readme = "README.md" -include = ["run.py"] [tool.poetry.dependencies] python = "~3.11" @@ -22,8 +21,8 @@ pylint = "^2.15" mypy = "^0.991" pytest = "^7.2.0" coveralls = "^3.3.1" -flake8-black = "^0.3.4" pytest-cov = "^4.0.0" +black = "^22.10.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index 00ac913..3bf70a7 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -71,7 +71,7 @@ "file": "stacosys/db/__init__.py", "settings": { - "buffer_size": 672, + "buffer_size": 701, "encoding": "UTF-8", "line_ending": "Unix" }, @@ -83,7 +83,7 @@ "paste", null, "AQAAAAAAAAAAAAAALQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/" ], [ 3, @@ -93,7 +93,7 @@ "characters": "\n\nd" }, "AwAAAC0AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAADAAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAAPC/" ], [ 4, @@ -103,7 +103,7 @@ "characters": "b" }, "AQAAADAAAAAAAAAAMQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAwAAAAAAAAADAAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMAAAAAAAAAAwAAAAAAAAAAAAAAAAAPC/" ], [ 5, @@ -113,7 +113,7 @@ "characters": " =" }, "AgAAADEAAAAAAAAAMgAAAAAAAAAAAAAAMgAAAAAAAAAzAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAxAAAAAAAAADEAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMQAAAAAAAAAxAAAAAAAAAAAAAAAAAPC/" ], [ 6, @@ -123,7 +123,7 @@ "characters": " None" }, "BQAAADMAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADYAAAAAAAAAAAAAADYAAAAAAAAANwAAAAAAAAAAAAAANwAAAAAAAAA4AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMwAAAAAAAAAzAAAAAAAAAAAAAAAAAPC/" ], [ 11, @@ -133,7 +133,7 @@ "characters": "\n" }, "AQAAAC4AAAAAAAAALwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAuAAAAAAAAAC4AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAAPC/" ], [ 12, @@ -141,7 +141,7 @@ "paste", null, "AQAAAC8AAAAAAAAASwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALwAAAAAAAAAvAAAAAAAAAAAAAAAAAPC/" ], [ 13, @@ -151,7 +151,7 @@ "characters": "\n" }, "AQAAAEsAAAAAAAAATAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwAAAAAAAABLAAAAAAAAAAAAAAAAAPC/" ], [ 15, @@ -161,7 +161,7 @@ "characters": "\n\n" }, "AgAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABWAAAAAAAAAFYAAAAAAAAAAAAAAAAAAAA" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAAAA" ], [ 16, @@ -169,7 +169,7 @@ "paste", null, "AQAAAFgAAAAAAAAA6wAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABYAAAAAAAAAFgAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAWAAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" ], [ 21, @@ -179,7 +179,7 @@ "characters": "i" }, "AgAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABoAAAAAAAAAAEAAABs", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABoAAAAAAAAAGcAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaAAAAAAAAABnAAAAAAAAAAAAAAAAAPC/" ], [ 29, @@ -189,7 +189,7 @@ "characters": "db" }, "AwAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAA8AAABkYi5pbml0KGRiX3VybClwAAAAAAAAAHEAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABvAAAAAAAAAH4AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbwAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" ], [ 30, @@ -199,7 +199,7 @@ "characters": " " }, "AgAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABxAAAAAAAAAHEAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcQAAAAAAAABxAAAAAAAAAAAAAAAAAPC/" ], [ 31, @@ -207,7 +207,7 @@ "left_delete", null, "AQAAAHIAAAAAAAAAcgAAAAAAAAABAAAAIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABzAAAAAAAAAHMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" ], [ 32, @@ -217,7 +217,7 @@ "characters": "=" }, "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAByAAAAAAAAAHIAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" ], [ 33, @@ -227,7 +227,7 @@ "characters": " " }, "AQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABzAAAAAAAAAHMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" ], [ 35, @@ -235,7 +235,7 @@ "", null, "AQAAAHQAAAAAAAAAjgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB0AAAAAAAAAHQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdAAAAAAAAAB0AAAAAAAAAAAAAAAAAPC/" ], [ 39, @@ -245,7 +245,7 @@ "characters": "db_" }, "BAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAB5AAAAAAAAABUAAAAnc3FsaXRlOi8vc3RvcmFnZS5kYid5AAAAAAAAAHoAAAAAAAAAAAAAAHoAAAAAAAAAewAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACNAAAAAAAAAHgAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjQAAAAAAAAB4AAAAAAAAAAAAAAAAAPC/" ], [ 42, @@ -259,7 +259,7 @@ "trigger": "db_uri" }, "AgAAAHgAAAAAAAAAeAAAAAAAAAADAAAAZGJfeAAAAAAAAAB+AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB7AAAAAAAAAHsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" ], [ 46, @@ -269,7 +269,7 @@ "characters": "\n" }, "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" ], [ 48, @@ -277,7 +277,7 @@ "trim_trailing_white_space", null, "AQAAAIAAAAAAAAAAgAAAAAAAAAAEAAAAICAgIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACEAAAAAAAAAIQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" ], [ 55, @@ -285,7 +285,7 @@ "reindent", null, "AQAAAIAAAAAAAAAAhAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" ], [ 56, @@ -295,7 +295,7 @@ "characters": "db." }, "AwAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAACGAAAAAAAAAIcAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACEAAAAAAAAAIQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" ], [ 57, @@ -305,7 +305,7 @@ "characters": "defi" }, "BAAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACHAAAAAAAAAIcAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" ], [ 58, @@ -319,7 +319,7 @@ "trigger": "define_table" }, "AgAAAIcAAAAAAAAAhwAAAAAAAAAEAAAAZGVmaYcAAAAAAAAAkwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACLAAAAAAAAAIsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" ], [ 59, @@ -329,7 +329,7 @@ "contents": "($0)" }, "AQAAAJMAAAAAAAAAlQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAkwAAAAAAAACTAAAAAAAAAAAAAAAAAPC/" ], [ 60, @@ -339,7 +339,7 @@ "contents": "\"$0\"" }, "AQAAAJQAAAAAAAAAlgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACUAAAAAAAAAJQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAlAAAAAAAAACUAAAAAAAAAAAAAAAAAPC/" ], [ 61, @@ -349,7 +349,7 @@ "characters": "comment" }, "BwAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAACaAAAAAAAAAJsAAAAAAAAAAAAAAJsAAAAAAAAAnAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACVAAAAAAAAAJUAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" ], [ 67, @@ -357,7 +357,7 @@ "left_delete", null, "AQAAAJ8AAAAAAAAAnwAAAAAAAABsAAAAICAgIGRiLmNvbm5lY3QoKQoKICAgIGZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKICAgIGRiLmNyZWF0ZV90YWJsZXMoW0NvbW1lbnRdLCBzYWZlPVRydWUp", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAALAQAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwEAAAAAAACfAAAAAAAAAAAAAAAAAPC/" ], [ 70, @@ -367,7 +367,7 @@ "characters": ";" }, "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAJ0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" ], [ 71, @@ -375,7 +375,7 @@ "left_delete", null, "AQAAAJ0AAAAAAAAAnQAAAAAAAAABAAAAOw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAngAAAAAAAACeAAAAAAAAAAAAAAAAAPC/" ], [ 72, @@ -385,7 +385,7 @@ "characters": "," }, "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACdAAAAAAAAAJ0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" ], [ 73, @@ -395,7 +395,7 @@ "characters": " " }, "AQAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAngAAAAAAAACeAAAAAAAAAAAAAAAAAPC/" ], [ 74, @@ -405,7 +405,7 @@ "characters": "Field" }, "BQAAAJ8AAAAAAAAAoAAAAAAAAAAAAAAAoAAAAAAAAAChAAAAAAAAAAAAAAChAAAAAAAAAKIAAAAAAAAAAAAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACkAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACfAAAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnwAAAAAAAACfAAAAAAAAAAAAAAAAAPC/" ], [ 75, @@ -415,7 +415,7 @@ "contents": "($0)" }, "AQAAAKQAAAAAAAAApgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACkAAAAAAAAAKQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApAAAAAAAAACkAAAAAAAAAAAAAAAAAPC/" ], [ 76, @@ -425,7 +425,7 @@ "contents": "\"$0\"" }, "AQAAAKUAAAAAAAAApwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAAPC/" ], [ 77, @@ -433,7 +433,7 @@ "paste", null, "AQAAAKYAAAAAAAAAqQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACmAAAAAAAAAKYAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApgAAAAAAAACmAAAAAAAAAAAAAAAAAPC/" ], [ 81, @@ -443,7 +443,7 @@ "characters": "," }, "AQAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqwAAAAAAAACrAAAAAAAAAAAAAAAAAPC/" ], [ 82, @@ -453,7 +453,7 @@ "characters": " F" }, "AgAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArAAAAAAAAACsAAAAAAAAAAAAAAAAAPC/" ], [ 83, @@ -463,7 +463,7 @@ "characters": "ield" }, "BAAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACuAAAAAAAAAK4AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" ], [ 84, @@ -473,7 +473,7 @@ "contents": "($0)" }, "AQAAALIAAAAAAAAAtAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAAAAAAAAALIAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgAAAAAAAACyAAAAAAAAAAAAAAAAAPC/" ], [ 85, @@ -483,7 +483,7 @@ "contents": "\"$0\"" }, "AQAAALMAAAAAAAAAtQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAAAAAAAAALMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAswAAAAAAAACzAAAAAAAAAAAAAAAAAPC/" ], [ 86, @@ -491,7 +491,7 @@ "paste", null, "AQAAALQAAAAAAAAAuwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" ], [ 88, @@ -501,7 +501,7 @@ "characters": "," }, "AQAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC8AAAAAAAAALwAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAAPC/" ], [ 89, @@ -511,7 +511,7 @@ "characters": " type=" }, "BgAAAL0AAAAAAAAAvgAAAAAAAAAAAAAAvgAAAAAAAAC/AAAAAAAAAAAAAAC/AAAAAAAAAMAAAAAAAAAAAAAAAMAAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAADCAAAAAAAAAAAAAADCAAAAAAAAAMMAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC9AAAAAAAAAL0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvQAAAAAAAAC9AAAAAAAAAAAAAAAAAPC/" ], [ 90, @@ -521,7 +521,7 @@ "contents": "\"$0\"" }, "AQAAAMMAAAAAAAAAxQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADDAAAAAAAAAMMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwwAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" ], [ 91, @@ -531,7 +531,7 @@ "characters": "date" }, "BAAAAMQAAAAAAAAAxQAAAAAAAAAAAAAAxQAAAAAAAADGAAAAAAAAAAAAAADGAAAAAAAAAMcAAAAAAAAAAAAAAMcAAAAAAAAAyAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADEAAAAAAAAAMQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAxAAAAAAAAADEAAAAAAAAAAAAAAAAAPC/" ], [ 95, @@ -541,7 +541,7 @@ "characters": ";" }, "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADKAAAAAAAAAMoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" ], [ 96, @@ -551,7 +551,7 @@ "characters": " " }, "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAAAAAAAAAMsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywAAAAAAAADLAAAAAAAAAAAAAAAAAPC/" ], [ 97, @@ -559,7 +559,7 @@ "left_delete", null, "AgAAAMsAAAAAAAAAywAAAAAAAAABAAAAIMoAAAAAAAAAygAAAAAAAAABAAAAOw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAAAAAAAAAMwAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" ], [ 98, @@ -569,7 +569,7 @@ "characters": "," }, "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADKAAAAAAAAAMoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" ], [ 99, @@ -579,7 +579,7 @@ "characters": " " }, "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAAAAAAAAAMsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywAAAAAAAADLAAAAAAAAAAAAAAAAAPC/" ], [ 104, @@ -587,7 +587,7 @@ "paste", null, "AQAAAMwAAAAAAAAA6QAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAAAAAAAAAMwAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" ], [ 110, @@ -595,7 +595,7 @@ "paste", null, "AgAAANMAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADbAAAAAAAAAAcAAABjcmVhdGVk", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAAAAAAAAANoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" ], [ 114, @@ -605,7 +605,7 @@ "characters": "," }, "AQAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpAAAAAAAAAOkAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" ], [ 115, @@ -615,7 +615,7 @@ "characters": " de" }, "AwAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA6wAAAAAAAADsAAAAAAAAAAAAAADsAAAAAAAAAO0AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAOoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAADqAAAAAAAAAAAAAAAAAPC/" ], [ 116, @@ -629,7 +629,7 @@ "trigger": "default=" }, "AgAAAOsAAAAAAAAA6wAAAAAAAAACAAAAZGXrAAAAAAAAAPMAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADtAAAAAAAAAO0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA7QAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" ], [ 117, @@ -639,7 +639,7 @@ "characters": "None" }, "BAAAAPMAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD1AAAAAAAAAAAAAAD1AAAAAAAAAPYAAAAAAAAAAAAAAPYAAAAAAAAA9wAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADzAAAAAAAAAPMAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" ], [ 127, @@ -647,7 +647,7 @@ "", null, "AgAAAPMAAAAAAAAA+gAAAAAAAAAAAAAA+gAAAAAAAAD6AAAAAAAAAAQAAABOb25l", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADzAAAAAAAAAPcAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wAAAAAAAAD3AAAAAAAAAAAAAAAAAPC/" ], [ 133, @@ -655,7 +655,7 @@ "left_delete", null, "AgAAAOoAAAAAAAAA6gAAAAAAAAARAAAAIGRlZmF1bHQ9REVGQVVMVCnpAAAAAAAAAOkAAAAAAAAAAQAAACw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAPsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" ], [ 135, @@ -665,7 +665,7 @@ "characters": "," }, "AQAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADqAAAAAAAAAOoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAADqAAAAAAAAAAAAAAAAAPC/" ], [ 136, @@ -675,7 +675,7 @@ "characters": " " }, "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrAAAAAAAAAOsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" ], [ 141, @@ -683,7 +683,7 @@ "paste", null, "AQAAAOsAAAAAAAAACQEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADrAAAAAAAAAOsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" ], [ 148, @@ -691,7 +691,7 @@ "paste", null, "AgAAAPIAAAAAAAAA+wAAAAAAAAAAAAAA+wAAAAAAAAD7AAAAAAAAAAgAAABub3RpZmllZA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADyAAAAAAAAAPoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8gAAAAAAAAD6AAAAAAAAAAAAAAAAAPC/" ], [ 153, @@ -701,7 +701,7 @@ "characters": "," }, "AQAAAAoBAAAAAAAACwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAKAQAAAAAAAAoBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACgEAAAAAAAAKAQAAAAAAAAAAAAAAAPC/" ], [ 154, @@ -711,7 +711,7 @@ "characters": " " }, "AQAAAAsBAAAAAAAADAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAALAQAAAAAAAAsBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwEAAAAAAAALAQAAAAAAAAAAAAAAAPC/" ], [ 155, @@ -721,7 +721,7 @@ "contents": "\"$0\"" }, "AQAAAAwBAAAAAAAADgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" ], [ 156, @@ -731,7 +731,7 @@ "file": "res://Packages/Default/Delete Left Right.sublime-macro" }, "AgAAAAwBAAAAAAAADAEAAAAAAAABAAAAIgwBAAAAAAAADAEAAAAAAAABAAAAIg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAANAQAAAAAAAA0BAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADQEAAAAAAAANAQAAAAAAAAAAAAAAAPC/" ], [ 157, @@ -741,7 +741,7 @@ "characters": "Field" }, "BQAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" ], [ 158, @@ -751,7 +751,7 @@ "characters": "-" }, "AQAAABEBAAAAAAAAEgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAARAQAAAAAAABEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" ], [ 159, @@ -759,7 +759,7 @@ "left_delete", null, "AQAAABEBAAAAAAAAEQEAAAAAAAABAAAALQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASAQAAAAAAABIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" ], [ 160, @@ -769,7 +769,7 @@ "contents": "($0)" }, "AQAAABEBAAAAAAAAEwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAARAQAAAAAAABEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" ], [ 161, @@ -779,7 +779,7 @@ "contents": "\"$0\"" }, "AQAAABIBAAAAAAAAFAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASAQAAAAAAABIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" ], [ 166, @@ -787,7 +787,7 @@ "paste", null, "AQAAABMBAAAAAAAAHgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAATAQAAAAAAABMBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" ], [ 171, @@ -797,7 +797,7 @@ "characters": "," }, "AQAAACABAAAAAAAAIQEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAgAQAAAAAAACABAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAPC/" ], [ 172, @@ -807,7 +807,7 @@ "characters": " " }, "AQAAACEBAAAAAAAAIgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAhAQAAAAAAACEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIQEAAAAAAAAhAQAAAAAAAAAAAAAAAPC/" ], [ 173, @@ -815,7 +815,7 @@ "paste", null, "AQAAACIBAAAAAAAANgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAiAQAAAAAAACIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIgEAAAAAAAAiAQAAAAAAAAAAAAAAAPC/" ], [ 176, @@ -825,7 +825,7 @@ "characters": "site" }, "BQAAADABAAAAAAAAMQEAAAAAAAAAAAAAMQEAAAAAAAAxAQAAAAAAAAQAAABuYW1lMQEAAAAAAAAyAQAAAAAAAAAAAAAyAQAAAAAAADMBAAAAAAAAAAAAADMBAAAAAAAANAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA0AQAAAAAAADABAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANAEAAAAAAAAwAQAAAAAAAAAAAAAAAPC/" ], [ 178, @@ -835,7 +835,7 @@ "characters": ";" }, "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" ], [ 179, @@ -843,7 +843,7 @@ "left_delete", null, "AQAAADYBAAAAAAAANgEAAAAAAAABAAAAOw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" ], [ 180, @@ -853,7 +853,7 @@ "characters": "," }, "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" ], [ 181, @@ -863,7 +863,7 @@ "characters": " " }, "AQAAADcBAAAAAAAAOAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" ], [ 182, @@ -871,7 +871,7 @@ "paste", null, "AQAAADgBAAAAAAAARwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA4AQAAAAAAADgBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" ], [ 190, @@ -879,7 +879,7 @@ "paste", null, "AQAAADgBAAAAAAAATAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA4AQAAAAAAADgBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" ], [ 192, @@ -887,7 +887,7 @@ "cut", null, "AQAAAEwBAAAAAAAATAEAAAAAAAAPAAAAYXV0aG9yX2dyYXZhdGFy", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABMAQAAAAAAAFsBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATAEAAAAAAABbAQAAAAAAAAAAAAAAAPC/" ], [ 197, @@ -895,7 +895,7 @@ "paste", null, "AgAAAD8BAAAAAAAATgEAAAAAAAAAAAAATgEAAAAAAABOAQAAAAAAAAsAAABhdXRob3Jfc2l0ZQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA/AQAAAAAAAEoBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAPwEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" ], [ 201, @@ -905,7 +905,7 @@ "characters": "," }, "AQAAADUBAAAAAAAANgEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA1AQAAAAAAADUBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" ], [ 202, @@ -915,7 +915,7 @@ "characters": " ef" }, "AwAAADYBAAAAAAAANwEAAAAAAAAAAAAANwEAAAAAAAA4AQAAAAAAAAAAAAA4AQAAAAAAADkBAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA2AQAAAAAAADYBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" ], [ 203, @@ -923,7 +923,7 @@ "left_delete", null, "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAZjcBAAAAAAAANwEAAAAAAAABAAAAZQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5AQAAAAAAADkBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" ], [ 204, @@ -933,7 +933,7 @@ "characters": "default=" }, "CAAAADcBAAAAAAAAOAEAAAAAAAAAAAAAOAEAAAAAAAA5AQAAAAAAAAAAAAA5AQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOwEAAAAAAAAAAAAAOwEAAAAAAAA8AQAAAAAAAAAAAAA8AQAAAAAAAD0BAAAAAAAAAAAAAD0BAAAAAAAAPgEAAAAAAAAAAAAAPgEAAAAAAAA/AQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA3AQAAAAAAADcBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" ], [ 205, @@ -943,7 +943,7 @@ "contents": "\"$0\"" }, "AQAAAD8BAAAAAAAAQQEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA/AQAAAAAAAD8BAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAPwEAAAAAAAA/AQAAAAAAAAAAAAAAAPC/" ], [ 208, @@ -951,7 +951,7 @@ "trim_trailing_white_space", null, "AQAAAFwBAAAAAAAAXAEAAAAAAAABAAAAIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABBAQAAAAAAAEEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQQEAAAAAAABBAQAAAAAAAAAAAAAAAPC/" ], [ 219, @@ -961,7 +961,7 @@ "characters": "\ndef" }, "BAAAAFcAAAAAAAAAWAAAAAAAAAAAAAAAWAAAAAAAAABZAAAAAAAAAAAAAABZAAAAAAAAAFoAAAAAAAAAAAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABXAAAAAAAAAFcAAAAAAAAAAAAAAACAZkA" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAgGZA" ], [ 220, @@ -971,7 +971,7 @@ "characters": " _" }, "AgAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABbAAAAAAAAAFsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAWwAAAAAAAABbAAAAAAAAAAAAAAAAAPC/" ], [ 221, @@ -981,7 +981,7 @@ "characters": "null" }, "BAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" ], [ 222, @@ -989,7 +989,7 @@ "left_delete", null, "BAAAAGAAAAAAAAAAYAAAAAAAAAABAAAAbF8AAAAAAAAAXwAAAAAAAAABAAAAbF4AAAAAAAAAXgAAAAAAAAABAAAAdV0AAAAAAAAAXQAAAAAAAAABAAAAbg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABhAAAAAAAAAGEAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAYQAAAAAAAABhAAAAAAAAAAAAAAAAAPC/" ], [ 223, @@ -999,7 +999,7 @@ "characters": "empty_s" }, "BwAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" ], [ 224, @@ -1009,7 +1009,7 @@ "characters": "tring" }, "BQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABpAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABkAAAAAAAAAGQAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAPC/" ], [ 225, @@ -1019,7 +1019,7 @@ "contents": "($0)" }, "AQAAAGkAAAAAAAAAawAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABpAAAAAAAAAGkAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" ], [ 227, @@ -1029,7 +1029,7 @@ "characters": ":" }, "AQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABrAAAAAAAAAGsAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" ], [ 228, @@ -1039,7 +1039,7 @@ "characters": "\nreturn" }, "CAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABsAAAAAAAAAGwAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbAAAAAAAAABsAAAAAAAAAAAAAAAAAPC/" ], [ 229, @@ -1049,7 +1049,7 @@ "characters": " " }, "AQAAAHcAAAAAAAAAeAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB3AAAAAAAAAHcAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwAAAAAAAAB3AAAAAAAAAAAAAAAAAPC/" ], [ 230, @@ -1059,7 +1059,7 @@ "contents": "\"$0\"" }, "AQAAAHgAAAAAAAAAegAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB4AAAAAAAAAHgAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAeAAAAAAAAAB4AAAAAAAAAAAAAAAAAPC/" ], [ 232, @@ -1069,7 +1069,7 @@ "characters": "\n" }, "AgAAAHoAAAAAAAAAewAAAAAAAAAAAAAAewAAAAAAAAB/AAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB6AAAAAAAAAHoAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAegAAAAAAAAB6AAAAAAAAAAAAAAAAAPC/" ], [ 234, @@ -1077,7 +1077,7 @@ "trim_trailing_white_space", null, "AQAAAHsAAAAAAAAAewAAAAAAAAAEAAAAICAgIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AAAAAAAAAH8AAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" ], [ 242, @@ -1087,7 +1087,7 @@ "characters": "\n" }, "BAAAAPAAAAAAAAAA8QAAAAAAAAAAAAAA8QAAAAAAAAD1AAAAAAAAAAAAAADxAAAAAAAAAPEAAAAAAAAABAAAACAgICDxAAAAAAAAAPkAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAPC/" ], [ 245, @@ -1097,7 +1097,7 @@ "characters": "\n" }, "AgAAADkBAAAAAAAAOgEAAAAAAAAAAAAAOgEAAAAAAABCAQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAA5AQAAAAAAADkBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" ], [ 250, @@ -1105,7 +1105,7 @@ "paste", null, "AgAAAHUBAAAAAAAAggEAAAAAAAAAAAAAggEAAAAAAACCAQAAAAAAAAIAAAAiIg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB1AQAAAAAAAHcBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdQEAAAAAAAB3AQAAAAAAAAAAAAAAAPC/" ], [ 252, @@ -1113,7 +1113,7 @@ "trim_trailing_white_space", null, "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAIO8AAAAAAAAA7wAAAAAAAAABAAAAIA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAQAAAAAAAIIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAggEAAAAAAACCAQAAAAAAAAAAAAAAAPC/" ], [ 269, @@ -1123,7 +1123,7 @@ "characters": "," }, "AQAAAJoBAAAAAAAAmwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAQAAAAAAAJoBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAmgEAAAAAAACaAQAAAAAAAAAAAAAAAPC/" ], [ 270, @@ -1133,7 +1133,7 @@ "characters": " " }, "AQAAAJsBAAAAAAAAnAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAQAAAAAAAJsBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAmwEAAAAAAACbAQAAAAAAAAAAAAAAAPC/" ], [ 271, @@ -1141,7 +1141,7 @@ "paste", null, "AQAAAJwBAAAAAAAAsQEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAQAAAAAAAJwBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnAEAAAAAAACcAQAAAAAAAAAAAAAAAPC/" ], [ 273, @@ -1151,7 +1151,7 @@ "characters": "," }, "AQAAALIBAAAAAAAAswEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAQAAAAAAALIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgEAAAAAAACyAQAAAAAAAAAAAAAAAPC/" ], [ 274, @@ -1161,7 +1161,7 @@ "characters": "\n" }, "AgAAALMBAAAAAAAAtAEAAAAAAAAAAAAAtAEAAAAAAAC8AQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAQAAAAAAALMBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAswEAAAAAAACzAQAAAAAAAAAAAAAAAPC/" ], [ 275, @@ -1171,7 +1171,7 @@ "characters": "Field" }, "BQAAALwBAAAAAAAAvQEAAAAAAAAAAAAAvQEAAAAAAAC+AQAAAAAAAAAAAAC+AQAAAAAAAL8BAAAAAAAAAAAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADBAQAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC8AQAAAAAAALwBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvAEAAAAAAAC8AQAAAAAAAAAAAAAAAPC/" ], [ 276, @@ -1181,7 +1181,7 @@ "contents": "($0)" }, "AQAAAMEBAAAAAAAAwwEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADBAQAAAAAAAMEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" ], [ 277, @@ -1191,7 +1191,7 @@ "contents": "\"$0\"" }, "AQAAAMIBAAAAAAAAxAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADCAQAAAAAAAMIBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwgEAAAAAAADCAQAAAAAAAAAAAAAAAPC/" ], [ 278, @@ -1201,7 +1201,7 @@ "characters": "content" }, "BwAAAMMBAAAAAAAAxAEAAAAAAAAAAAAAxAEAAAAAAADFAQAAAAAAAAAAAADFAQAAAAAAAMYBAAAAAAAAAAAAAMYBAAAAAAAAxwEAAAAAAAAAAAAAxwEAAAAAAADIAQAAAAAAAAAAAADIAQAAAAAAAMkBAAAAAAAAAAAAAMkBAAAAAAAAygEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADDAQAAAAAAAMMBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwwEAAAAAAADDAQAAAAAAAAAAAAAAAPC/" ], [ 280, @@ -1211,7 +1211,7 @@ "characters": "," }, "AQAAAMsBAAAAAAAAzAEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADLAQAAAAAAAMsBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywEAAAAAAADLAQAAAAAAAAAAAAAAAPC/" ], [ 281, @@ -1221,7 +1221,7 @@ "characters": " type\"\"" }, "BwAAAMwBAAAAAAAAzQEAAAAAAAAAAAAAzQEAAAAAAADOAQAAAAAAAAAAAADOAQAAAAAAAM8BAAAAAAAAAAAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA0AEAAAAAAADRAQAAAAAAAAAAAADRAQAAAAAAANIBAAAAAAAAAAAAANIBAAAAAAAA0wEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADMAQAAAAAAAMwBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAEAAAAAAADMAQAAAAAAAAAAAAAAAPC/" ], [ 283, @@ -1231,7 +1231,7 @@ "characters": "=" }, "AQAAANEBAAAAAAAA0gEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADRAQAAAAAAANEBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QEAAAAAAADRAQAAAAAAAAAAAAAAAPC/" ], [ 287, @@ -1241,7 +1241,7 @@ "characters": ")" }, "AQAAANUBAAAAAAAA1gEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADVAQAAAAAAANUBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" ], [ 290, @@ -1251,7 +1251,7 @@ "characters": "text" }, "BAAAANMBAAAAAAAA1AEAAAAAAAAAAAAA1AEAAAAAAADVAQAAAAAAAAAAAADVAQAAAAAAANYBAAAAAAAAAAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAQAAAAAAANMBAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wEAAAAAAADTAQAAAAAAAAAAAAAAAPC/" ], [ 305, @@ -1261,7 +1261,7 @@ "characters": "confiu" }, "BwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAUAAABzZXR1cIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" ], [ 306, @@ -1269,7 +1269,7 @@ "left_delete", null, "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAdQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACGAAAAAAAAAIYAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" ], [ 307, @@ -1279,7 +1279,7 @@ "characters": "gure" }, "BAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACFAAAAAAAAAIUAAAAAAAAAAAAAAAAA8L8" + "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" ], [ 5, @@ -1287,7 +1287,7 @@ "left_delete", null, "AQAAAG0BAAAAAAAAbQEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", - "AQAAAAAAAAABAAAAbQEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAG0BAAAAAAAAhAEAAAAAAAAAAAAAAADwvw" ], [ 8, @@ -1295,7 +1295,7 @@ "left_delete", null, "AQAAAIcBAAAAAAAAhwEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", - "AQAAAAAAAAABAAAAhwEAAAAAAACeAQAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcBAAAAAAAAngEAAAAAAAAAAAAAAADwvw" ], [ 13, @@ -1303,7 +1303,7 @@ "left_delete", null, "AwAAAFgAAAAAAAAAWAAAAAAAAAAiAAAAZGVmIF9lbXB0eV9zdHJpbmcoKToKICAgIHJldHVybiAiIlcAAAAAAAAAVwAAAAAAAAABAAAAClYAAAAAAAAAVgAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAegAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHoAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" ], [ 20, @@ -1313,7 +1313,7 @@ "characters": "time" }, "BAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMwAAAAAAAAAzAAAAAAAAAAAAAAAAADwvw" ], [ 23, @@ -1323,7 +1323,7 @@ "characters": "time" }, "BAAAABgBAAAAAAAAGQEAAAAAAAAAAAAAGQEAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAABsBAAAAAAAAHAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGAEAAAAAAAAYAQAAAAAAAAAAAAAAIINA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABgBAAAAAAAAGAEAAAAAAAAAAAAAACCDQA" ], [ 26, @@ -1333,7 +1333,7 @@ "characters": "time" }, "BAAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAADwvw" ], [ 29, @@ -1341,7 +1341,7 @@ "revert", null, "AgAAAAAAAAAAAAAAAAAAAAAAAACZAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkYiA9IE5vbmUKCmRlZiBjb25maWd1cmUoZGJfdXJpKToKICAgIGRiID0gREFMKGRiX3VyaSkKICAgIGRiLmRlZmluZV90YWJsZSgiY29tbWVudCIsIEZpZWxkKCJ1cmwiKSwgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJub3RpZmllZCIsIHR5cGU9ImRhdGV0aW1lIiksRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLCBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgRmllbGQoImNvbnRlbnQiLCB0eXBlPSJ0ZXh0IikpCgAAAAAAAAAA0gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPwAAAAAAAAA/AAAAAAAAAAAAAAAAADwvw" ], [ 36, @@ -1351,7 +1351,7 @@ "characters": "dal" }, "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABTAAAAAAAAAAQAAABOb25lUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" ], [ 37, @@ -1359,7 +1359,7 @@ "left_delete", null, "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", - "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" ], [ 38, @@ -1369,7 +1369,7 @@ "characters": "dal" }, "AwAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgAAAAAAAABSAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" ], [ 39, @@ -1377,7 +1377,7 @@ "left_delete", null, "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", - "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" ], [ 40, @@ -1387,7 +1387,7 @@ "characters": "DAL)" }, "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAFUAAAAAAAAAVgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgAAAAAAAABSAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" ], [ 41, @@ -1395,7 +1395,7 @@ "left_delete", null, "AQAAAFUAAAAAAAAAVQAAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" ], [ 42, @@ -1405,7 +1405,7 @@ "contents": "($0)" }, "AQAAAFUAAAAAAAAAVwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" ], [ 56, @@ -1415,7 +1415,7 @@ "characters": "\ndb." }, "BQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAjAAAAAAAAACNAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" ], [ 57, @@ -1425,7 +1425,7 @@ "characters": "c" }, "AQAAAI0AAAAAAAAAjgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAADwvw" ], [ 58, @@ -1435,7 +1435,7 @@ "characters": "on" }, "AgAAAI4AAAAAAAAAjwAAAAAAAAAAAAAAjwAAAAAAAACQAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjgAAAAAAAACOAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI4AAAAAAAAAjgAAAAAAAAAAAAAAAADwvw" ], [ 59, @@ -1443,7 +1443,7 @@ "left_delete", null, "AgAAAI8AAAAAAAAAjwAAAAAAAAABAAAAbo4AAAAAAAAAjgAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJAAAAAAAAAAkAAAAAAAAAAAAAAAAADwvw" ], [ 62, @@ -1451,7 +1451,7 @@ "left_delete", null, "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYg", - "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" ], [ 63, @@ -1461,7 +1461,7 @@ "characters": "atabase" }, "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" ], [ 71, @@ -1469,7 +1469,7 @@ "paste", null, "AgAAAHsAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACDAAAAAAAAAAIAAABkYg", - "AQAAAAAAAAABAAAAewAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAfQAAAAAAAAAAAAAAAADwvw" ], [ 75, @@ -1477,7 +1477,7 @@ "paste", null, "AgAAAJ8AAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACnAAAAAAAAAAIAAABkYg", - "AQAAAAAAAAABAAAAoQAAAAAAAACfAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKEAAAAAAAAAnwAAAAAAAAAAAAAAAADwvw" ], [ 79, @@ -1487,7 +1487,7 @@ "characters": "None" }, "BQAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABZAAAAAAAAAAUAAABEQUwoKVkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWAAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAXQAAAAAAAAAAAAAAAADwvw" ], [ 83, @@ -1497,7 +1497,7 @@ "characters": "db_dal" }, "BwAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABOAAAAAAAAAAgAAABkYXRhYmFzZU4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAATQAAAAAAAAAAAAAAAADwvw" ], [ 94, @@ -1505,7 +1505,7 @@ "paste", null, "AgAAAHgAAAAAAAAAfgAAAAAAAAAAAAAAfgAAAAAAAAB+AAAAAAAAAAgAAABkYXRhYmFzZQ", - "AQAAAAAAAAABAAAAeAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHgAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" ], [ 98, @@ -1513,7 +1513,7 @@ "right_delete", null, "AQAAAJEAAAAAAAAAkQAAAAAAAAAEAAAAZGIuYw", - "AQAAAAAAAAABAAAAlQAAAAAAAACRAAAAAAAAAAAAAAAAAEJA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAkQAAAAAAAAAAAAAAAABCQA" ], [ 100, @@ -1521,7 +1521,7 @@ "left_delete", null, "AQAAAIwAAAAAAAAAjAAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAAAAAA" ], [ 104, @@ -1529,7 +1529,7 @@ "paste", null, "AgAAAJUAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACbAAAAAAAAAAgAAABkYXRhYmFzZQ", - "AQAAAAAAAAABAAAAlQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAnQAAAAAAAAAAAAAAAADwvw" ], [ 106, @@ -1537,7 +1537,7 @@ "trim_trailing_white_space", null, "AQAAAIwAAAAAAAAAjAAAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAmwAAAAAAAACbAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJsAAAAAAAAAmwAAAAAAAAAAAAAAAADwvw" ], [ 114, @@ -1547,7 +1547,7 @@ "characters": "\ndb_dal." }, "CQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" ], [ 122, @@ -1557,7 +1557,7 @@ "characters": "DAL" }, "BAAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABXAAAAAAAAAAQAAABOb25lVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVgAAAAAAAABaAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAWgAAAAAAAAAAAAAAAADwvw" ], [ 123, @@ -1567,7 +1567,7 @@ "contents": "($0)" }, "AQAAAFkAAAAAAAAAWwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" ], [ 130, @@ -1575,7 +1575,7 @@ "left_delete", null, "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" ], [ 131, @@ -1585,7 +1585,7 @@ "characters": "." }, "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 135, @@ -1593,7 +1593,7 @@ "right_delete", null, "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAgAAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 136, @@ -1603,7 +1603,7 @@ "characters": "." }, "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 142, @@ -1613,7 +1613,7 @@ "characters": "#" }, "AQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" ], [ 152, @@ -1621,7 +1621,7 @@ "left_delete", null, "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" ], [ 153, @@ -1631,7 +1631,7 @@ "characters": "." }, "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 154, @@ -1641,7 +1641,7 @@ "characters": "set" }, "AwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" ], [ 155, @@ -1649,7 +1649,7 @@ "left_delete", null, "AwAAAIIAAAAAAAAAggAAAAAAAAABAAAAdIEAAAAAAAAAgQAAAAAAAAABAAAAZYAAAAAAAAAAgAAAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" ], [ 156, @@ -1659,7 +1659,7 @@ "characters": "ur" }, "AgAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" ], [ 157, @@ -1667,7 +1667,7 @@ "left_delete", null, "AwAAAIEAAAAAAAAAgQAAAAAAAAABAAAAcoAAAAAAAAAAgAAAAAAAAAABAAAAdX8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAggAAAAAAAACCAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIAAAAAAAAAggAAAAAAAAAAAAAAAADwvw" ], [ 158, @@ -1677,7 +1677,7 @@ "characters": ".co" }, "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 159, @@ -1687,7 +1687,7 @@ "characters": "n" }, "AQAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAggAAAAAAAACCAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIAAAAAAAAAggAAAAAAAAAAAAAAAADwvw" ], [ 171, @@ -1697,7 +1697,7 @@ "characters": "None" }, "BAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAAWwAAAAAAAABcAAAAAAAAAAAAAABcAAAAAAAAAF0AAAAAAAAAAAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWgAAAAAAAABaAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoAAAAAAAAAWgAAAAAAAAAAAAAAAADwvw" ], [ 181, @@ -1705,7 +1705,7 @@ "left_delete", null, "AQAAAFoAAAAAAAAAWgAAAAAAAAAEAAAATm9uZQ", - "AQAAAAAAAAABAAAAWgAAAAAAAABeAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoAAAAAAAAAXgAAAAAAAAAAAAAAAADwvw" ], [ 189, @@ -1715,7 +1715,7 @@ "characters": "global" }, "BwAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB6AAAAAAAAAAoAAABkYl9kYWwuY29uegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwAAAAAAAAB5AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAeQAAAAAAAAAAAAAAAADwvw" ], [ 190, @@ -1725,7 +1725,7 @@ "characters": " db_dal" }, "BwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 193, @@ -1733,7 +1733,7 @@ "right_delete", null, "AQAAAIsAAAAAAAAAiwAAAAAAAAABAAAAIw", - "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAEJA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsAAAAAAAAAiwAAAAAAAAAAAAAAAABCQA" ], [ 203, @@ -1741,7 +1741,7 @@ "right_delete", null, "AwAAAE0AAAAAAAAATQAAAAAAAAAPAAAAZGJfZGFsID0gREFMKCkKTQAAAAAAAABNAAAAAAAAAAEAAAAKTQAAAAAAAABNAAAAAAAAAAEAAAAK", - "AQAAAAAAAAABAAAAXAAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFwAAAAAAAAATQAAAAAAAAAAAAAAAADwvw" ], [ 206, @@ -1749,7 +1749,7 @@ "revert", null, "AgAAAAAAAAAAAAAAAAAAAAAAAADgAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkZWYgY29uZmlndXJlKGRiX3VyaSk6CiAgICBnbG9iYWwgZGJfZGFsCiAgICBkYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAiY29tbWVudCIsCiAgICAgICAgRmllbGQoInVybCIpLAogICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJwdWJsaXNoZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfZ3JhdmF0YXIiKSwKICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICkKAAAAAAAAAADhAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" ], [ 214, @@ -1759,7 +1759,7 @@ "characters": "db" }, "AgAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" ], [ 215, @@ -1769,7 +1769,7 @@ "characters": " =" }, "AgAAAE8AAAAAAAAAUAAAAAAAAAAAAAAAUAAAAAAAAABRAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" ], [ 216, @@ -1777,7 +1777,7 @@ "left_delete", null, "AwAAAFAAAAAAAAAAUAAAAAAAAAABAAAAPU8AAAAAAAAATwAAAAAAAAABAAAAIE4AAAAAAAAATgAAAAAAAAABAAAAYg", - "AQAAAAAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFEAAAAAAAAAUQAAAAAAAAAAAAAAAADwvw" ], [ 217, @@ -1787,7 +1787,7 @@ "characters": "a" }, "AQAAAE4AAAAAAAAATwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" ], [ 218, @@ -1795,7 +1795,7 @@ "left_delete", null, "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYQ", - "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" ], [ 219, @@ -1805,7 +1805,7 @@ "characters": "atabase" }, "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgAAAAAAAABOAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" ], [ 220, @@ -1815,7 +1815,7 @@ "characters": " =" }, "AgAAAFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVQAAAAAAAABVAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" ], [ 221, @@ -1825,7 +1825,7 @@ "characters": " " }, "AQAAAFcAAAAAAAAAWAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFcAAAAAAAAAVwAAAAAAAAAAAAAAAADwvw" ], [ 223, @@ -1835,7 +1835,7 @@ "characters": "Databas" }, "BwAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAABdAAAAAAAAAF4AAAAAAAAAAAAAAF4AAAAAAAAAXwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWAAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" ], [ 224, @@ -1845,7 +1845,7 @@ "characters": "e" }, "AQAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXwAAAAAAAABfAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8AAAAAAAAAXwAAAAAAAAAAAAAAAADwvw" ], [ 225, @@ -1855,7 +1855,7 @@ "contents": "($0)" }, "AQAAAGAAAAAAAAAAYgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGAAAAAAAAAAYAAAAAAAAAAAAAAAAADwvw" ], [ 227, @@ -1865,7 +1865,7 @@ "characters": "\n" }, "AQAAAGIAAAAAAAAAYwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYgAAAAAAAABiAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGIAAAAAAAAAYgAAAAAAAAAAAAAAAADwvw" ], [ 230, @@ -1875,7 +1875,7 @@ "characters": "\nclass" }, "BgAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAGYAAAAAAAAAZwAAAAAAAAAAAAAAZwAAAAAAAABoAAAAAAAAAAAAAABoAAAAAAAAAGkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGMAAAAAAAAAYwAAAAAAAAAAAAAAAAAAAA" ], [ 231, @@ -1885,7 +1885,7 @@ "characters": " Database" }, "CQAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGkAAAAAAAAAaQAAAAAAAAAAAAAAAADwvw" ], [ 232, @@ -1895,7 +1895,7 @@ "contents": "($0)" }, "AQAAAHIAAAAAAAAAdAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAcgAAAAAAAAAAAAAAAADwvw" ], [ 234, @@ -1903,7 +1903,7 @@ "left_delete", null, "AgAAAHMAAAAAAAAAcwAAAAAAAAABAAAAKXIAAAAAAAAAcgAAAAAAAAABAAAAKA", - "AQAAAAAAAAABAAAAdAAAAAAAAAB0AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHQAAAAAAAAAdAAAAAAAAAAAAAAAAADwvw" ], [ 235, @@ -1913,7 +1913,7 @@ "characters": ":" }, "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAcgAAAAAAAAAAAAAAAADwvw" ], [ 236, @@ -1923,7 +1923,7 @@ "characters": "\n" }, "AgAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" ], [ 243, @@ -1931,7 +1931,7 @@ "indent", null, "DgAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAlAAAAAAAAACYAAAAAAAAAAAAAACqAAAAAAAAAK4AAAAAAAAAAAAAAMcAAAAAAAAAywAAAAAAAAAAAAAA5AAAAAAAAADoAAAAAAAAAAAAAAD7AAAAAAAAAP8AAAAAAAAAAAAAABUBAAAAAAAAGQEAAAAAAAAAAAAARAEAAAAAAABIAQAAAAAAAAAAAAB0AQAAAAAAAHgBAAAAAAAAAAAAAKUBAAAAAAAAqQEAAAAAAAAAAAAAxwEAAAAAAADLAQAAAAAAAAAAAADpAQAAAAAAAO0BAAAAAAAAAAAAAA8CAAAAAAAAEwIAAAAAAAAAAAAAOgIAAAAAAAA+AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAeQAAAAAAAAAMAgAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHkAAAAAAAAADAIAAAAAAAAAAAAAAAAAAA" ], [ 250, @@ -1941,7 +1941,7 @@ "characters": "self," }, "BgAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsAAAAAAAAAiwAAAAAAAAAAAAAAAADwvw" ], [ 254, @@ -1951,7 +1951,7 @@ "characters": "\n\ndef" }, "CAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgeQAAAAAAAAB6AAAAAAAAAAAAAAB6AAAAAAAAAHsAAAAAAAAAAAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" ], [ 255, @@ -1961,7 +1961,7 @@ "characters": " __init" }, "BwAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHwAAAAAAAAAfAAAAAAAAAAAAAAAAADwvw" ], [ 256, @@ -1971,7 +1971,7 @@ "characters": "__" }, "AgAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" ], [ 257, @@ -1981,7 +1981,7 @@ "contents": "($0)" }, "AQAAAIUAAAAAAAAAhwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" ], [ 259, @@ -1991,7 +1991,7 @@ "characters": ":" }, "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcAAAAAAAAAhwAAAAAAAAAAAAAAAADwvw" ], [ 260, @@ -2001,7 +2001,7 @@ "characters": "\n" }, "AwAAAIgAAAAAAAAAiQAAAAAAAAAAAAAAiQAAAAAAAACNAAAAAAAAAAAAAACNAAAAAAAAAJEAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIgAAAAAAAAAiAAAAAAAAAAAAAAAAADwvw" ], [ 261, @@ -2011,7 +2011,7 @@ "characters": "slef" }, "BAAAAJEAAAAAAAAAkgAAAAAAAAAAAAAAkgAAAAAAAACTAAAAAAAAAAAAAACTAAAAAAAAAJQAAAAAAAAAAAAAAJQAAAAAAAAAlQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJEAAAAAAAAAkQAAAAAAAAAAAAAAAADwvw" ], [ 262, @@ -2019,7 +2019,7 @@ "left_delete", null, "AwAAAJQAAAAAAAAAlAAAAAAAAAABAAAAZpMAAAAAAAAAkwAAAAAAAAABAAAAZZIAAAAAAAAAkgAAAAAAAAABAAAAbA", - "AQAAAAAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAlQAAAAAAAAAAAAAAAADwvw" ], [ 263, @@ -2029,7 +2029,7 @@ "characters": "elf" }, "AwAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJIAAAAAAAAAkgAAAAAAAAAAAAAAAADwvw" ], [ 267, @@ -2037,7 +2037,7 @@ "right_delete", null, "AQAAAHUAAAAAAAAAdQAAAAAAAAAhAAAAICAgIGRlZiBfX2luaXRfXygpOgogICAgICAgIHNlbGYK", - "AQAAAAAAAAABAAAAdQAAAAAAAACWAAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHUAAAAAAAAAlgAAAAAAAAAAAAAAAAAAAA" ], [ 270, @@ -2045,7 +2045,7 @@ "reindent", null, "AQAAAHUAAAAAAAAAeQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdQAAAAAAAAB1AAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHUAAAAAAAAAdQAAAAAAAAAAAAAAAAAAAA" ], [ 271, @@ -2055,7 +2055,7 @@ "characters": "db" }, "AgAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAeQAAAAAAAAB5AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHkAAAAAAAAAeQAAAAAAAAAAAAAAAADwvw" ], [ 272, @@ -2065,7 +2065,7 @@ "characters": " =" }, "AgAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAewAAAAAAAAAAAAAAAADwvw" ], [ 273, @@ -2073,7 +2073,7 @@ "left_delete", null, "AgAAAHwAAAAAAAAAfAAAAAAAAAABAAAAPXsAAAAAAAAAewAAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAfQAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH0AAAAAAAAAfQAAAAAAAAAAAAAAAADwvw" ], [ 274, @@ -2083,7 +2083,7 @@ "characters": "_dal" }, "BAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAewAAAAAAAAAAAAAAAADwvw" ], [ 275, @@ -2091,7 +2091,7 @@ "left_delete", null, "AQAAAH4AAAAAAAAAfgAAAAAAAAABAAAAbA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 276, @@ -2101,7 +2101,7 @@ "characters": "l" }, "AQAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4AAAAAAAAAfgAAAAAAAAAAAAAAAADwvw" ], [ 277, @@ -2115,7 +2115,7 @@ "trigger": "db_dal" }, "AgAAAHkAAAAAAAAAeQAAAAAAAAAGAAAAZGJfZGFseQAAAAAAAAB/AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 278, @@ -2125,7 +2125,7 @@ "characters": "\n" }, "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 281, @@ -2135,7 +2135,7 @@ "characters": " =" }, "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAQAAAAgICAggAAAAAAAAACBAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" ], [ 282, @@ -2145,7 +2145,7 @@ "characters": " None" }, "BQAAAIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQAAAAAAAACBAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIEAAAAAAAAAgQAAAAAAAAAAAAAAAADwvw" ], [ 291, @@ -2153,7 +2153,7 @@ "right_delete", null, "AQAAALAAAAAAAAAAsAAAAAAAAAANAAAAZ2xvYmFsIGRiX2RhbA", - "AQAAAAAAAAABAAAAsAAAAAAAAAC9AAAAAAAAAP///////+9/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAAAAAAAAAAvQAAAAAAAAD////////vfw" ], [ 293, @@ -2161,7 +2161,7 @@ "left_delete", null, "AQAAAKcAAAAAAAAApwAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAqAAAAAAAAACoAAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAAAAA" ], [ 296, @@ -2171,7 +2171,7 @@ "characters": "sl" }, "AgAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuAAAAAAAAAC4AAAAAAAAAAAAAAAAAFJA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAABSQA" ], [ 297, @@ -2179,7 +2179,7 @@ "left_delete", null, "AQAAALkAAAAAAAAAuQAAAAAAAAABAAAAbA", - "AQAAAAAAAAABAAAAugAAAAAAAAC6AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALoAAAAAAAAAugAAAAAAAAAAAAAAAADwvw" ], [ 298, @@ -2189,7 +2189,7 @@ "characters": "elf." }, "BAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuQAAAAAAAAC5AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALkAAAAAAAAAuQAAAAAAAAAAAAAAAADwvw" ], [ 301, @@ -2199,7 +2199,7 @@ "characters": "self." }, "BQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANoAAAAAAAAA2gAAAAAAAAAAAAAAAADwvw" ], [ 303, @@ -2207,7 +2207,7 @@ "trim_trailing_white_space", null, "AQAAAKcAAAAAAAAApwAAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAN8AAAAAAAAA3wAAAAAAAAAAAAAAAADwvw" ], [ 309, @@ -2215,7 +2215,7 @@ "cut", null, "AQAAAE0AAAAAAAAATQAAAAAAAAAVAAAAZGF0YWJhc2UgPSBEYXRhYmFzZSgp", - "AQAAAAAAAAABAAAAYgAAAAAAAABNAAAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGIAAAAAAAAATQAAAAAAAAAAAAAAAAAAAA" ], [ 310, @@ -2223,7 +2223,7 @@ "right_delete", null, "AgAAAE0AAAAAAAAATQAAAAAAAAABAAAACk0AAAAAAAAATQAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" ], [ 316, @@ -2233,7 +2233,7 @@ "characters": "\n" }, "AQAAADUCAAAAAAAANgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAAAA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUCAAAAAAAANQIAAAAAAAAAAAAAAAAAAA" ], [ 317, @@ -2241,7 +2241,7 @@ "paste", null, "AQAAADYCAAAAAAAASwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANgIAAAAAAAA2AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADYCAAAAAAAANgIAAAAAAAAAAAAAAADwvw" ], [ 321, @@ -2249,7 +2249,7 @@ "revert", null, "AgAAAAAAAAAAAAAAAAAAAAAAAABLAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpjbGFzcyBEYXRhYmFzZToKCiAgICBkYl9kYWwgPSBOb25lCgogICAgZGVmIGNvbmZpZ3VyZShzZWxmLGRiX3VyaSk6CiAgICAgICAgc2VsZi5kYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgICAgIHNlbGYuZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAgICAgImNvbW1lbnQiLAogICAgICAgICAgICBGaWVsZCgidXJsIiksCiAgICAgICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoIm5vdGlmaWVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX3NpdGUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9ncmF2YXRhciIpLAogICAgICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICAgICApCgpkYXRhYmFzZSA9IERhdGFiYXNlKCkAAAAAAAAAAE8CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAoGdA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUCAAAAAAAANQIAAAAAAAAAAAAAAKBnQA" ], [ 336, @@ -2259,7 +2259,7 @@ "characters": "Dal" }, "BAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAgAAABEYXRhYmFzZVUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAXAAAAAAAAAAAAAAAAADwvw" ], [ 340, @@ -2269,7 +2269,7 @@ "characters": "Db" }, "AwAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAMAAABEYWxVAAAAAAAAAFYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVwAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFcAAAAAAAAAVAAAAAAAAAAAAAAAAADwvw" ], [ 341, @@ -2277,7 +2277,7 @@ "left_delete", null, "AgAAAFUAAAAAAAAAVQAAAAAAAAABAAAAYlQAAAAAAAAAVAAAAAAAAAABAAAARA", - "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" ], [ 342, @@ -2287,7 +2287,7 @@ "characters": "Db" }, "AgAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABWAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAVAAAAAAAAAAAAAAAAADwvw" ], [ 343, @@ -2297,7 +2297,7 @@ "characters": "Dal" }, "AwAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" ], [ 355, @@ -2305,7 +2305,7 @@ "paste", null, "AgAAAEECAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABGAgAAAAAAAAgAAABEYXRhYmFzZQ", - "AQAAAAAAAAABAAAAQQIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAASQIAAAAAAAAAAAAAAADwvw" ], [ 369, @@ -2315,7 +2315,7 @@ "characters": "Databse" }, "CAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAUAAABEYkRhbFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAFgAAAAAAAAAAAAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" ], [ 370, @@ -2323,7 +2323,7 @@ "left_delete", null, "AgAAAFoAAAAAAAAAWgAAAAAAAAABAAAAZVkAAAAAAAAAWQAAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAAWwAAAAAAAABbAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFsAAAAAAAAAWwAAAAAAAAAAAAAAAADwvw" ], [ 371, @@ -2333,7 +2333,7 @@ "characters": "ase" }, "AwAAAFkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" ], [ 381, @@ -2341,7 +2341,7 @@ "paste", null, "AgAAAEQCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABMAgAAAAAAAAUAAABEYkRhbA", - "AQAAAAAAAAABAAAARAIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAASQIAAAAAAAAAAAAAAADwvw" ], [ 386, @@ -2351,7 +2351,7 @@ "characters": "db" }, "AwAAADkCAAAAAAAAOgIAAAAAAAAAAAAAOgIAAAAAAAA6AgAAAAAAAAgAAABkYXRhYmFzZToCAAAAAAAAOwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQQIAAAAAAAA5AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAOQIAAAAAAAAAAAAAAADwvw" ], [ 401, @@ -2361,7 +2361,7 @@ "characters": "\n" }, "AQAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADcCAAAAAAAANwIAAAAAAAAAAAAAAADwvw" ], [ 402, @@ -2369,7 +2369,7 @@ "reindent", null, "AQAAADgCAAAAAAAAQAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADgCAAAAAAAAOAIAAAAAAAAAAAAAAADwvw" ], [ 403, @@ -2377,7 +2377,7 @@ "left_delete", null, "AQAAADwCAAAAAAAAPAIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" ], [ 404, @@ -2387,7 +2387,7 @@ "characters": "def" }, "AwAAADwCAAAAAAAAPQIAAAAAAAAAAAAAPQIAAAAAAAA+AgAAAAAAAAAAAAA+AgAAAAAAAD8CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPAIAAAAAAAA8AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADwCAAAAAAAAPAIAAAAAAAAAAAAAAADwvw" ], [ 405, @@ -2397,7 +2397,7 @@ "characters": " " }, "AQAAAD8CAAAAAAAAQAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAD8CAAAAAAAAPwIAAAAAAAAAAAAAAADwvw" ], [ 406, @@ -2407,7 +2407,7 @@ "characters": "dal" }, "AwAAAEACAAAAAAAAQQIAAAAAAAAAAAAAQQIAAAAAAABCAgAAAAAAAAAAAABCAgAAAAAAAEMCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" ], [ 407, @@ -2417,7 +2417,7 @@ "contents": "($0)" }, "AQAAAEMCAAAAAAAARQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEMCAAAAAAAAQwIAAAAAAAAAAAAAAADwvw" ], [ 410, @@ -2427,7 +2427,7 @@ "characters": "self" }, "BAAAAEQCAAAAAAAARQIAAAAAAAAAAAAARQIAAAAAAABGAgAAAAAAAAAAAABGAgAAAAAAAEcCAAAAAAAAAAAAAEcCAAAAAAAASAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARAIAAAAAAABEAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAARAIAAAAAAAAAAAAAAADwvw" ], [ 412, @@ -2437,7 +2437,7 @@ "characters": ":" }, "AQAAAEkCAAAAAAAASgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASQIAAAAAAABJAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEkCAAAAAAAASQIAAAAAAAAAAAAAAADwvw" ], [ 413, @@ -2447,7 +2447,7 @@ "characters": "\nreturn" }, "CQAAAEoCAAAAAAAASwIAAAAAAAAAAAAASwIAAAAAAABPAgAAAAAAAAAAAABPAgAAAAAAAFMCAAAAAAAAAAAAAFMCAAAAAAAAVAIAAAAAAAAAAAAAVAIAAAAAAABVAgAAAAAAAAAAAABVAgAAAAAAAFYCAAAAAAAAAAAAAFYCAAAAAAAAVwIAAAAAAAAAAAAAVwIAAAAAAABYAgAAAAAAAAAAAABYAgAAAAAAAFkCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASgIAAAAAAABKAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoCAAAAAAAASgIAAAAAAAAAAAAAAADwvw" ], [ 414, @@ -2457,7 +2457,7 @@ "characters": " self." }, "BgAAAFkCAAAAAAAAWgIAAAAAAAAAAAAAWgIAAAAAAABbAgAAAAAAAAAAAABbAgAAAAAAAFwCAAAAAAAAAAAAAFwCAAAAAAAAXQIAAAAAAAAAAAAAXQIAAAAAAABeAgAAAAAAAAAAAABeAgAAAAAAAF8CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWQIAAAAAAABZAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkCAAAAAAAAWQIAAAAAAAAAAAAAAADwvw" ], [ 415, @@ -2471,7 +2471,7 @@ "trigger": "db_dal" }, "AQAAAF8CAAAAAAAAZQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXwIAAAAAAABfAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8CAAAAAAAAXwIAAAAAAAAAAAAAAADwvw" ], [ 420, @@ -2479,7 +2479,7 @@ "revert", null, "AgAAAAAAAAAAAAAAAAAAAAAAAAB3AgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKZGIgPSBEYXRhYmFzZSgpCgAAAAAAAAAAeAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZgIAAAAAAABmAgAAAAAAAAAAAAAAQG1A" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGYCAAAAAAAAZgIAAAAAAAAAAAAAAEBtQA" ], [ 437, @@ -2489,7 +2489,7 @@ "characters": "database" }, "CQAAAGgCAAAAAAAAaQIAAAAAAAAAAAAAaQIAAAAAAABpAgAAAAAAAAIAAABkYmkCAAAAAAAAagIAAAAAAAAAAAAAagIAAAAAAABrAgAAAAAAAAAAAABrAgAAAAAAAGwCAAAAAAAAAAAAAGwCAAAAAAAAbQIAAAAAAAAAAAAAbQIAAAAAAABuAgAAAAAAAAAAAABuAgAAAAAAAG8CAAAAAAAAAAAAAG8CAAAAAAAAcAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaAIAAAAAAABqAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGgCAAAAAAAAagIAAAAAAAAAAAAAAADwvw" ], [ 439, @@ -2499,7 +2499,7 @@ "characters": "db" }, "AgAAAH4CAAAAAAAAfwIAAAAAAAAAAAAAfwIAAAAAAACAAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgIAAAAAAAB+AgAAAAAAAAAAAAAAAFJA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4CAAAAAAAAfgIAAAAAAAAAAAAAAABSQA" ], [ 440, @@ -2509,7 +2509,7 @@ "characters": " =" }, "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" ], [ 441, @@ -2519,7 +2519,7 @@ "characters": " data" }, "BQAAAIICAAAAAAAAgwIAAAAAAAAAAAAAgwIAAAAAAACEAgAAAAAAAAAAAACEAgAAAAAAAIUCAAAAAAAAAAAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAggIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIICAAAAAAAAggIAAAAAAAAAAAAAAADwvw" ], [ 442, @@ -2529,7 +2529,7 @@ "characters": "base" }, "BAAAAIcCAAAAAAAAiAIAAAAAAAAAAAAAiAIAAAAAAACJAgAAAAAAAAAAAACJAgAAAAAAAIoCAAAAAAAAAAAAAIoCAAAAAAAAiwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwIAAAAAAACHAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcCAAAAAAAAhwIAAAAAAAAAAAAAAADwvw" ], [ 443, @@ -2539,7 +2539,7 @@ "characters": "." }, "AQAAAIsCAAAAAAAAjAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsCAAAAAAAAiwIAAAAAAAAAAAAAAADwvw" ], [ 444, @@ -2553,7 +2553,7 @@ "trigger": "db_dal" }, "AQAAAIwCAAAAAAAAkgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjAIAAAAAAACMAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIwCAAAAAAAAjAIAAAAAAAAAAAAAAADwvw" ], [ 460, @@ -2561,7 +2561,7 @@ "revert", null, "AgAAAAAAAAAAAAAAAAAAAAAAAACSAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKCmRhdGFiYXNlID0gRGF0YWJhc2UoKQpkYiA9IGRhdGFiYXNlLmRiX2RhbAAAAAAAAAAAkwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgIAAAAAAACSAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJICAAAAAAAAkgIAAAAAAAAAAAAAAADwvw" ], [ 469, @@ -2571,7 +2571,7 @@ "characters": "DAL" }, "BAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABtAAAAAAAAAAQAAABOb25lbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbAAAAAAAAABwAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGwAAAAAAAAAcAAAAAAAAAAAAAAAAADwvw" ], [ 470, @@ -2581,7 +2581,7 @@ "contents": "($0)" }, "AQAAAG8AAAAAAAAAcQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbwAAAAAAAABvAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAG8AAAAAAAAAbwAAAAAAAAAAAAAAAADwvw" ], [ 482, @@ -2589,7 +2589,7 @@ "left_delete", null, "AgAAADgCAAAAAAAAOAIAAAAAAAAuAAAACiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbDcCAAAAAAAANwIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAZgIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGYCAAAAAAAAOAIAAAAAAAAAAAAAAADwvw" ], [ 503, @@ -2599,7 +2599,7 @@ "characters": "\n\n" }, "BQAAADcCAAAAAAAAOAIAAAAAAAAAAAAAOAIAAAAAAABAAgAAAAAAAAAAAABAAgAAAAAAAEECAAAAAAAAAAAAAEECAAAAAAAASQIAAAAAAAAAAAAAOAIAAAAAAAA4AgAAAAAAAAgAAAAgICAgICAgIA", - "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADcCAAAAAAAANwIAAAAAAAAAAAAAAADwvw" ], [ 504, @@ -2607,7 +2607,7 @@ "left_delete", null, "AQAAAD0CAAAAAAAAPQIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAQQIAAAAAAABBAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAQQIAAAAAAAAAAAAAAADwvw" ], [ 505, @@ -2617,7 +2617,7 @@ "characters": "def" }, "AwAAAD0CAAAAAAAAPgIAAAAAAAAAAAAAPgIAAAAAAAA/AgAAAAAAAAAAAAA/AgAAAAAAAEACAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPQIAAAAAAAA9AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAD0CAAAAAAAAPQIAAAAAAAAAAAAAAADwvw" ], [ 506, @@ -2627,7 +2627,7 @@ "characters": " " }, "AQAAAEACAAAAAAAAQQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" ], [ 507, @@ -2637,7 +2637,7 @@ "characters": "get" }, "AwAAAEECAAAAAAAAQgIAAAAAAAAAAAAAQgIAAAAAAABDAgAAAAAAAAAAAABDAgAAAAAAAEQCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQQIAAAAAAABBAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAQQIAAAAAAAAAAAAAAADwvw" ], [ 508, @@ -2647,7 +2647,7 @@ "contents": "($0)" }, "AQAAAEQCAAAAAAAARgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARAIAAAAAAABEAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAARAIAAAAAAAAAAAAAAADwvw" ], [ 509, @@ -2657,7 +2657,7 @@ "characters": "self" }, "BAAAAEUCAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABHAgAAAAAAAAAAAABHAgAAAAAAAEgCAAAAAAAAAAAAAEgCAAAAAAAASQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARQIAAAAAAABFAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEUCAAAAAAAARQIAAAAAAAAAAAAAAADwvw" ], [ 511, @@ -2667,7 +2667,7 @@ "characters": ":" }, "AQAAAEoCAAAAAAAASwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASgIAAAAAAABKAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoCAAAAAAAASgIAAAAAAAAAAAAAAADwvw" ], [ 512, @@ -2677,7 +2677,7 @@ "characters": "\nreturn" }, "CQAAAEsCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABQAgAAAAAAAAAAAABQAgAAAAAAAFQCAAAAAAAAAAAAAFQCAAAAAAAAVQIAAAAAAAAAAAAAVQIAAAAAAABWAgAAAAAAAAAAAABWAgAAAAAAAFcCAAAAAAAAAAAAAFcCAAAAAAAAWAIAAAAAAAAAAAAAWAIAAAAAAABZAgAAAAAAAAAAAABZAgAAAAAAAFoCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASwIAAAAAAABLAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEsCAAAAAAAASwIAAAAAAAAAAAAAAADwvw" ], [ 513, @@ -2687,7 +2687,7 @@ "characters": " sek" }, "BAAAAFoCAAAAAAAAWwIAAAAAAAAAAAAAWwIAAAAAAABcAgAAAAAAAAAAAABcAgAAAAAAAF0CAAAAAAAAAAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWgIAAAAAAABaAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoCAAAAAAAAWgIAAAAAAAAAAAAAAADwvw" ], [ 514, @@ -2697,7 +2697,7 @@ "characters": "f" }, "AQAAAF4CAAAAAAAAXwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXgIAAAAAAABeAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF4CAAAAAAAAXgIAAAAAAAAAAAAAAADwvw" ], [ 515, @@ -2705,7 +2705,7 @@ "left_delete", null, "AgAAAF4CAAAAAAAAXgIAAAAAAAABAAAAZl0CAAAAAAAAXQIAAAAAAAABAAAAaw", - "AQAAAAAAAAABAAAAXwIAAAAAAABfAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8CAAAAAAAAXwIAAAAAAAAAAAAAAADwvw" ], [ 516, @@ -2715,7 +2715,7 @@ "characters": "lf.d" }, "BAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAGACAAAAAAAAYQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXQIAAAAAAABdAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF0CAAAAAAAAXQIAAAAAAAAAAAAAAADwvw" ], [ 517, @@ -2729,7 +2729,7 @@ "trigger": "db_dal" }, "AgAAAGACAAAAAAAAYAIAAAAAAAABAAAAZGACAAAAAAAAZgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYQIAAAAAAABhAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGECAAAAAAAAYQIAAAAAAAAAAAAAAADwvw" ], [ 524, @@ -2739,7 +2739,7 @@ "characters": "get" }, "BAAAAI0CAAAAAAAAjgIAAAAAAAAAAAAAjgIAAAAAAACOAgAAAAAAAAYAAABkYl9kYWyOAgAAAAAAAI8CAAAAAAAAAAAAAI8CAAAAAAAAkAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQIAAAAAAACTAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0CAAAAAAAAkwIAAAAAAAAAAAAAAADwvw" ], [ 525, @@ -2749,7 +2749,7 @@ "contents": "($0)" }, "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkAIAAAAAAAAAAAAAAADwvw" ], [ 530, @@ -2757,7 +2757,7 @@ "right_delete", null, "AQAAAJACAAAAAAAAkAIAAAAAAAACAAAAKCk", - "AQAAAAAAAAABAAAAkAIAAAAAAACSAgAAAAAAAP///////+9/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkgIAAAAAAAD////////vfw" ], [ 537, @@ -2767,7 +2767,7 @@ "contents": "($0)" }, "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAIGNA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkAIAAAAAAAAAAAAAACBjQA" ], [ 549, @@ -2775,7 +2775,7 @@ "left_delete", null, "AQAAAIACAAAAAAAAgAIAAAAAAAABAAAAYg", - "AQAAAAAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIECAAAAAAAAgQIAAAAAAAAAAAAAAADwvw" ], [ 550, @@ -2785,7 +2785,7 @@ "characters": "al" }, "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" ], [ 552, @@ -2793,7 +2793,7 @@ "right_delete", null, "AgAAAJECAAAAAAAAkQIAAAAAAAABAAAAKJECAAAAAAAAkQIAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAAkQIAAAAAAACRAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJECAAAAAAAAkQIAAAAAAAAAAAAAAADwvw" ], [ 559, @@ -2803,7 +2803,7 @@ "characters": "db" }, "AwAAAH8CAAAAAAAAgAIAAAAAAAAAAAAAgAIAAAAAAACAAgAAAAAAAAMAAABkYWyAAgAAAAAAAIECAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfwIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8CAAAAAAAAggIAAAAAAAAAAAAAAADwvw" ], [ 577, @@ -2813,7 +2813,7 @@ "characters": "\n" }, "AQAAAH4CAAAAAAAAfwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgIAAAAAAAB+AgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4CAAAAAAAAfgIAAAAAAAAAAAAAAADwvw" ], [ 581, @@ -2823,7 +2823,7 @@ "characters": "al" }, "AwAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACCAgAAAAAAAAEAAABiggIAAAAAAACDAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIECAAAAAAAAggIAAAAAAAAAAAAAAADwvw" ], [ 583, @@ -2831,7 +2831,7 @@ "left_delete", null, "AQAAAH4CAAAAAAAAfgIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAfwIAAAAAAAB/AgAAAAAAAAAAAAAAADtA" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8CAAAAAAAAfwIAAAAAAAAAAAAAAAA7QA" ], [ 590, @@ -2839,7 +2839,7 @@ "left_delete", null, "AgAAAIECAAAAAAAAgQIAAAAAAAABAAAAbIACAAAAAAAAgAIAAAAAAAABAAAAYQ", - "AQAAAAAAAAABAAAAggIAAAAAAACCAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIICAAAAAAAAggIAAAAAAAAAAAAAAADwvw" ], [ 591, @@ -2849,7 +2849,7 @@ "characters": "b" }, "AQAAAIACAAAAAAAAgQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" ], [ 600, @@ -2857,7 +2857,7 @@ "", null, "AQAAALQAAAAAAAAAwQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" ], [ 602, @@ -2867,7 +2867,7 @@ "characters": "," }, "AQAAALQAAAAAAAAAtQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" ], [ 603, @@ -2877,7 +2877,7 @@ "characters": " " }, "AQAAALUAAAAAAAAAtgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtQAAAAAAAAC1AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALUAAAAAAAAAtQAAAAAAAAAAAAAAAADwvw" ], [ 612, @@ -2887,7 +2887,7 @@ "characters": "True" }, "BQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAUAAABGYWxzZb8AAAAAAAAAwAAAAAAAAAAAAAAAwAAAAAAAAADBAAAAAAAAAAAAAADBAAAAAAAAAMIAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAL4AAAAAAAAAwwAAAAAAAAAAAAAAAADwvw" ], [ 623, @@ -2897,2716 +2897,1887 @@ "characters": "False" }, "BgAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAQAAABUcnVlvwAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAMEAAAAAAAAAAAAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADDAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "stacosys/run.py", - "settings": - { - "buffer_size": 2472, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "toggle_breakpoint", - null, - "AQAAAIUFAAAAAAAAhQUAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGI3MDRhOWFjIC8vCg", - "AQAAAAAAAAABAAAAjAUAAAAAAACMBQAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "toggle_breakpoint", - null, - "AQAAAIUFAAAAAAAAvwUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgUAAAAAAACSBQAAAAAAAAAAAAAAAPC/" - ], - [ - 25, - 1, - "toggle_breakpoint", - null, - "AQAAAIUFAAAAAAAAhQUAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDQwOGIxMTUzIC8vCg", - "AQAAAAAAAAABAAAAnwUAAAAAAACfBQAAAAAAAAAAAAAAAPC/" - ], - [ - 28, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADQCQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBhcmdwYXJzZQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHN5cwoKZnJvbSBzdGFjb3N5cy5kYiBpbXBvcnQgZGF0YWJhc2UsIGRhbwpmcm9tIHN0YWNvc3lzLmludGVyZmFjZSBpbXBvcnQgYXBpLCBhcHAsIGZvcm0KZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2Uud2ViIGltcG9ydCBhZG1pbgpmcm9tIHN0YWNvc3lzLnNlcnZpY2UgaW1wb3J0IGNvbmZpZywgbWFpbGVyLCByc3MKZnJvbSBzdGFjb3N5cy5zZXJ2aWNlLmNvbmZpZ3VyYXRpb24gaW1wb3J0IENvbmZpZ1BhcmFtZXRlcgoKCiMgY29uZmlndXJlIGxvZ2dpbmcKZGVmIGNvbmZpZ3VyZV9sb2dnaW5nKGxldmVsKToKICAgIHJvb3RfbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoKQogICAgcm9vdF9sb2dnZXIuc2V0TGV2ZWwobGV2ZWwpCiAgICBoYW5kbGVyID0gbG9nZ2luZy5TdHJlYW1IYW5kbGVyKCkKICAgIGhhbmRsZXIuc2V0TGV2ZWwobGV2ZWwpCiAgICBmb3JtYXR0ZXIgPSBsb2dnaW5nLkZvcm1hdHRlcigiWyUoYXNjdGltZSlzXSAlKG5hbWUpcyAlKGxldmVsbmFtZSlzICUobWVzc2FnZSlzIikKICAgIGhhbmRsZXIuc2V0Rm9ybWF0dGVyKGZvcm1hdHRlcikKICAgIHJvb3RfbG9nZ2VyLmFkZEhhbmRsZXIoaGFuZGxlcikKCgpkZWYgc3RhY29zeXNfc2VydmVyKGNvbmZpZ19wYXRobmFtZSk6CiAgICAjIGNvbmZpZ3VyZSBsb2dnaW5nCiAgICBsb2dnZXIgPSBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXykKICAgIGNvbmZpZ3VyZV9sb2dnaW5nKGxvZ2dpbmcuSU5GTykKICAgIGxvZ2dpbmcuZ2V0TG9nZ2VyKCJ3ZXJremV1ZyIpLmxldmVsID0gbG9nZ2luZy5XQVJOSU5HCgogICAgIyBjaGVjayBjb25maWcgZmlsZSBleGlzdHMKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShjb25maWdfcGF0aG5hbWUpOgogICAgICAgIGxvZ2dlci5lcnJvcigiQ29uZmlndXJhdGlvbiBmaWxlICclcycgbm90IGZvdW5kLiIsIGNvbmZpZ19wYXRobmFtZSkKICAgICAgICBzeXMuZXhpdCgxKQoKICAgICMgbG9hZCBhbmQgY2hlY2sgY29uZmlnCiAgICBjb25maWcubG9hZChjb25maWdfcGF0aG5hbWUpCiAgICBpc19jb25maWdfb2ssIGVycmV1cl9jb25maWcgPSBjb25maWcuY2hlY2soKQogICAgaWYgbm90IGlzX2NvbmZpZ19vazoKICAgICAgICBsb2dnZXIuZXJyb3IoIkNvbmZpZ3VyYXRpb24gaW5jb3JyZWN0ZSAnJXMnIiwgZXJyZXVyX2NvbmZpZykKICAgICAgICBzeXMuZXhpdCgxKQogICAgbG9nZ2VyLmluZm8oY29uZmlnKQoKICAgICMgaW5pdGlhbGl6ZSBkYXRhYmFzZQogICAgZGF0YWJhc2UuY29uZmlndXJlKGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLkRCKSkKCiAgICByID0gZGFvLmZpbmRfY29tbWVudF9ieV9pZCgxKQoKICAgIGxvZ2dlci5pbmZvKCJTdGFydCBTdGFjb3N5cyBhcHBsaWNhdGlvbiIpCgogICAgIyBnZW5lcmF0ZSBSU1MKICAgIHJzcy5jb25maWd1cmUoCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuUlNTX0ZJTEUpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNJVEVfTkFNRSksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9QUk9UTyksCiAgICAgICAgY29uZmlnLmdldChDb25maWdQYXJhbWV0ZXIuU0lURV9VUkwpLAogICAgKQogICAgcnNzLmdlbmVyYXRlKCkKCiAgICAjIGNvbmZpZ3VyZSBtYWlsZXIKICAgIG1haWxlci5jb25maWd1cmVfc210cCgKICAgICAgICBjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TTVRQX0hPU1QpLAogICAgICAgIGNvbmZpZy5nZXRfaW50KENvbmZpZ1BhcmFtZXRlci5TTVRQX1BPUlQpLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfTE9HSU4pLAogICAgICAgIGNvbmZpZy5nZXQoQ29uZmlnUGFyYW1ldGVyLlNNVFBfUEFTU1dPUkQpLAogICAgKQogICAgbWFpbGVyLmNvbmZpZ3VyZV9kZXN0aW5hdGlvbihjb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5TSVRFX0FETUlOX0VNQUlMKSkKICAgIG1haWxlci5jaGVjaygpCgogICAgbG9nZ2VyLmluZm8oInN0YXJ0IGludGVyZmFjZXMgJXMgJXMgJXMiLCBhcGksIGZvcm0sIGFkbWluKQoKICAgICMgc3RhcnQgRmxhc2sKICAgIGFwcC5ydW4oCiAgICAgICAgaG9zdD1jb25maWcuZ2V0KENvbmZpZ1BhcmFtZXRlci5IVFRQX0hPU1QpLAogICAgICAgIHBvcnQ9Y29uZmlnLmdldF9pbnQoQ29uZmlnUGFyYW1ldGVyLkhUVFBfUE9SVCksCiAgICAgICAgZGVidWc9RmFsc2UsCiAgICAgICAgdXNlX3JlbG9hZGVyPUZhbHNlLAogICAgKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcigpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCJjb25maWciLCBoZWxwPSJjb25maWcgcGF0aCBuYW1lIikKICAgIGFyZ3MgPSBwYXJzZXIucGFyc2VfYXJncygpCiAgICBzdGFjb3N5c19zZXJ2ZXIoYXJncy5jb25maWcpCgAAAAAAAAAA0AkAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQUAAAAAAACFBQAAAAAAAAAAAAAAAPC/" - ], - [ - 33, - 1, - "right_delete", - null, - "AQAAAGEFAAAAAAAAYQUAAAAAAAAjAAAACiAgICByID0gZGFvLmZpbmRfY29tbWVudF9ieV9pZCgxKQo", - "AQAAAAAAAAABAAAAYQUAAAAAAACEBQAAAAAAAAAAAAAAkHJA" - ], - [ - 43, - 1, - "right_delete", - null, - "AQAAAHgAAAAAAAAAeAAAAAAAAAAFAAAAZGFvLCA", - "AQAAAAAAAAABAAAAeAAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "config-dev.ini", - "settings": - { - "buffer_size": 657, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 5, - 1, - "insert", - { - "characters": "\nsq" - }, - "AwAAAEUAAAAAAAAARgAAAAAAAAAAAAAARgAAAAAAAABHAAAAAAAAAAAAAABHAAAAAAAAAEgAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABFAAAAAAAAAEUAAAAAAAAAAAAAAAAA8L8" - ], - [ - 6, - 2, - "left_delete", - null, - "AgAAAEcAAAAAAAAARwAAAAAAAAABAAAAcUYAAAAAAAAARgAAAAAAAAABAAAAcw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABIAAAAAAAAAEgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 7, - 1, - "insert", - { - "characters": "dbçsq" - }, - "BQAAAEYAAAAAAAAARwAAAAAAAAAAAAAARwAAAAAAAABIAAAAAAAAAAAAAABIAAAAAAAAAEkAAAAAAAAAAAAAAEkAAAAAAAAASgAAAAAAAAAAAAAASgAAAAAAAABLAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABGAAAAAAAAAEYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 8, - 3, - "left_delete", - null, - "AwAAAEoAAAAAAAAASgAAAAAAAAABAAAAcUkAAAAAAAAASQAAAAAAAAABAAAAc0gAAAAAAAAASAAAAAAAAAACAAAAw6c", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 9, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAEgAAAAAAAAASQAAAAAAAAAAAAAASQAAAAAAAABKAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABIAAAAAAAAAEgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 10, - 1, - "insert", - { - "characters": " " - }, - "AQAAAEoAAAAAAAAASwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABKAAAAAAAAAEoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 12, - 1, - "", - null, - "AQAAAEsAAAAAAAAAXgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABLAAAAAAAAAEsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 18, - 1, - "paste", - null, - "AgAAAFQAAAAAAAAAXQAAAAAAAAAAAAAAXQAAAAAAAABdAAAAAAAAAAoAAABzdG9yYWdlLmRi", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABUAAAAAAAAAF4AAAAAAAAAAAAAAAAA8L8" - ], - [ - 25, - 1, - "insert", - { - "characters": "2" - }, - "AQAAAFYAAAAAAAAAVwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABWAAAAAAAAAFYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 5, - 1, - "left_delete", - null, - "AQAAAFYAAAAAAAAAVgAAAAAAAAABAAAAMg", - "AQAAAAAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "right_delete", - null, - "AQAAACsAAAAAAAAAKwAAAAAAAAAaAAAAZGJfc3FsaXRlX2ZpbGUgPSBkYi5zcWxpdGU", - "AQAAAAAAAAABAAAARQAAAAAAAAArAAAAAAAAAAAAAAAAAAAA" - ], - [ - 10, - 1, - "left_delete", - null, - "AQAAACoAAAAAAAAAKgAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAKwAAAAAAAAArAAAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": "2" - }, - "AQAAADsAAAAAAAAAPAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOwAAAAAAAAA7AAAAAAAAAAAAAAAAAPC/" + "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAL4AAAAAAAAAwgAAAAAAAAAAAAAAAADwvw" ], [ 14, 1, - "left_delete", - null, - "AQAAADsAAAAAAAAAOwAAAAAAAAABAAAAMg", - "AQAAAAAAAAABAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "dbmigration/migrate_from_3.3_to_4.0.py", - "settings": - { - "buffer_size": 1022, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ + "insert", + { + "characters": "," + }, + "AQAAAJEAAAAAAAAAkgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACRAAAAAAAAAJEAAAAAAAAAAAAAAAAA8L8" + ], [ - 12, + 15, 1, - "right_delete", - null, - "AQAAAPMBAAAAAAAA8wEAAAAAAAADAAAAVkFS", - "AQAAAAAAAAABAAAA8wEAAAAAAAD2AQAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": " migra" + }, + "BgAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACSAAAAAAAAAJIAAAAAAAAAAAAAAAAA8L8" ], [ 16, 1, "insert", { - "characters": "512" + "characters": "te" }, - "BAAAAPgBAAAAAAAA+QEAAAAAAAAAAAAA+QEAAAAAAAD5AQAAAAAAAAMAAAAyNTX5AQAAAAAAAPoBAAAAAAAAAAAAAPoBAAAAAAAA+wEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+AEAAAAAAAD7AQAAAAAAAAAAAAAAAPC/" + "AgAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACYAAAAAAAAAJgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 17, + 1, + "insert", + { + "characters": "=False" + }, + "BgAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACcAAAAAAAAAAAAAACcAAAAAAAAAJ0AAAAAAAAAAAAAAJ0AAAAAAAAAngAAAAAAAAAAAAAAngAAAAAAAACfAAAAAAAAAAAAAACfAAAAAAAAAKAAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 19, + 1, + "left_delete", + null, + "AQAAAJkAAAAAAAAAmQAAAAAAAAABAAAAZQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 20, + 1, + "insert", + { + "characters": "ion" + }, + "AwAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" ], [ 21, - 1, - "right_delete", + 2, + "left_delete", null, - "AQAAAP0BAAAAAAAA/QEAAAAAAAAIAAAATk9UIE5VTEw", - "AQAAAAAAAAABAAAA/QEAAAAAAAAFAgAAAAAAAAAAAAAAAPC/" + "AgAAAJsAAAAAAAAAmwAAAAAAAAABAAAAbpoAAAAAAAAAmgAAAAAAAAABAAAAbw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 22, + 1, + "insert", + { + "characters": "e" + }, + "AQAAAJoAAAAAAAAAmwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" ], [ 23, - 1, - "right_delete", + 2, + "left_delete", null, - "AQAAAPwBAAAAAAAA/AEAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAA/AEAAAAAAAD8AQAAAAAAAAAAAAAAAPC/" + "AgAAAJoAAAAAAAAAmgAAAAAAAAABAAAAZZkAAAAAAAAAmQAAAAAAAAABAAAAaQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAAAAAAAAAJsAAAAAAAAAAAAAAAAA8L8" ], [ - 27, + 24, 1, "insert", { - "characters": "TIMESTAM" + "characters": "e" }, - "CQAAAAsCAAAAAAAADAIAAAAAAAAAAAAADAIAAAAAAAAMAgAAAAAAAAgAAABEQVRFVElNRQwCAAAAAAAADQIAAAAAAAAAAAAADQIAAAAAAAAOAgAAAAAAAAAAAAAOAgAAAAAAAA8CAAAAAAAAAAAAAA8CAAAAAAAAEAIAAAAAAAAAAAAAEAIAAAAAAAARAgAAAAAAAAAAAAARAgAAAAAAABICAAAAAAAAAAAAABICAAAAAAAAEwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACwIAAAAAAAATAgAAAAAAAAAAAAAAAPC/" + "AQAAAJkAAAAAAAAAmgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" ], [ 28, 1, "insert", { - "characters": "P" + "characters": "mir" }, - "AQAAABMCAAAAAAAAFAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEwIAAAAAAAATAgAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "paste", - null, - "AgAAACICAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAArAgAAAAAAABEAAABEQVRFVElNRSBOT1QgTlVMTA", - "AQAAAAAAAAABAAAAIgIAAAAAAAAzAgAAAAAAAAAAAAAAAPC/" - ], - [ - 40, - 1, - "paste", - null, - "AgAAADsCAAAAAAAARAIAAAAAAAAAAAAARAIAAAAAAABEAgAAAAAAAAgAAABEQVRFVElNRQ", - "AQAAAAAAAAABAAAAOwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" - ], - [ - 49, - 1, - "paste", - null, - "AgAAAFYCAAAAAAAAXwIAAAAAAAAAAAAAXwIAAAAAAABfAgAAAAAAABUAAABWQVJDSEFSKDI1NSkgTk9UIE5VTEw", - "AQAAAAAAAAABAAAAVgIAAAAAAABrAgAAAAAAAAAAAAAAAPC/" - ], - [ - 52, - 1, - "paste", - null, - "AgAAAHECAAAAAAAAegIAAAAAAAAAAAAAegIAAAAAAAB6AgAAAAAAABUAAABWQVJDSEFSKDI1NSkgTk9UIE5VTEw", - "AQAAAAAAAAABAAAAcQIAAAAAAACGAgAAAAAAAAAAAAAAAPC/" - ], - [ - 55, - 1, - "paste", - null, - "AgAAAJACAAAAAAAAmQIAAAAAAAAAAAAAmQIAAAAAAACZAgAAAAAAAAwAAAB2YXJjaGFyKDI1NSk", - "AQAAAAAAAAABAAAAkAIAAAAAAACcAgAAAAAAAAAAAAAAAPC/" - ], - [ - 60, - 1, - "left_delete", - null, - "AQAAAKsCAAAAAAAAqwIAAAAAAAAJAAAAIE5PVCBOVUxM", - "AQAAAAAAAAABAAAAqwIAAAAAAAC0AgAAAAAAAAAAAAAAAPC/" - ], - [ - 66, - 1, - "right_delete", - null, - "AQAAAHIBAAAAAAAAcgEAAAAAAAAQAAAARFJPUCBUQUJMRSBzaXRlOw", - "AQAAAAAAAAABAAAAggEAAAAAAAByAQAAAAAAAAAAAAAAAAAA" - ], - [ - 67, - 1, - "left_delete", - null, - "AQAAAHEBAAAAAAAAcQEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAcgEAAAAAAAByAQAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "stacosys/db/dao.py", - "settings": - { - "buffer_size": 2026, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 4, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAALwAAAAAAAAAvQAAAAAAAAAAAAAAvQAAAAAAAADBAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAAPC/" - ], - [ - 7, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BgAAAEgAAAAAAAAASQAAAAAAAAAAAAAAvgAAAAAAAAC+AAAAAAAAAAQAAAAgICAgSQAAAAAAAABKAAAAAAAAAAAAAABKAAAAAAAAAEsAAAAAAAAAAAAAAEsAAAAAAAAATAAAAAAAAAAAAAAATAAAAAAAAABNAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASAAAAAAAAABIAAAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": " stacosys.db" - }, - "DAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAAUQAAAAAAAABSAAAAAAAAAAAAAABSAAAAAAAAAFMAAAAAAAAAAAAAAFMAAAAAAAAAVAAAAAAAAAAAAAAAVAAAAAAAAABVAAAAAAAAAAAAAABVAAAAAAAAAFYAAAAAAAAAAAAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "insert", - { - "characters": " imo" - }, - "BAAAAFkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAFwAAAAAAAAAXQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWQAAAAAAAABZAAAAAAAAAAAAAAAAAPC/" - ], - [ - 10, - 1, - "left_delete", - null, - "AQAAAFwAAAAAAAAAXAAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": "port" - }, - "BAAAAFwAAAAAAAAAXQAAAAAAAAAAAAAAXQAAAAAAAABeAAAAAAAAAAAAAABeAAAAAAAAAF8AAAAAAAAAAAAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXAAAAAAAAABcAAAAAAAAAAAAAAAAAPC/" - ], - [ - 12, - 1, - "insert", - { - "characters": " " - }, - "AQAAAGAAAAAAAAAAYQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 18, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.db\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":4,\"character\":24},\"symbolLabel\":\"db\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "db" - }, - "AQAAAGEAAAAAAAAAYwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYQAAAAAAAABhAAAAAAAAAAAAAAAAAPC/" - ], - [ - 27, - 1, - "reindent", - null, - "AQAAANgAAAAAAAAA3AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" - ], - [ - 28, - 1, - "insert", - { - "characters": "db." - }, - "AwAAANwAAAAAAAAA3QAAAAAAAAAAAAAA3QAAAAAAAADeAAAAAAAAAAAAAADeAAAAAAAAAN8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3AAAAAAAAADcAAAAAAAAAAAAAAAAAPC/" + "BAAAAM0AAAAAAAAAzgAAAAAAAAAAAAAAzgAAAAAAAADOAAAAAAAAAAUAAABGYWxzZc4AAAAAAAAAzwAAAAAAAAAAAAAAzwAAAAAAAADQAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADNAAAAAAAAANIAAAAAAAAAAAAAAAAA8L8" ], [ 29, 1, "left_delete", null, - "AQAAAN4AAAAAAAAA3gAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + "AQAAAM8AAAAAAAAAzwAAAAAAAAABAAAAcg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" ], [ 30, 1, - "insert_snippet", + "insert", { - "contents": "($0)" + "characters": "rgation" }, - "AQAAAN4AAAAAAAAA4AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAAPC/" + "BwAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA0AAAAAAAAADRAAAAAAAAAAAAAADRAAAAAAAAANIAAAAAAAAAAAAAANIAAAAAAAAA0wAAAAAAAAAAAAAA0wAAAAAAAADUAAAAAAAAAAAAAADUAAAAAAAAANUAAAAAAAAAAAAAANUAAAAAAAAA1gAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADPAAAAAAAAAM8AAAAAAAAAAAAAAAAA8L8" ], [ - 33, + 32, 1, - "right_delete", + "left_delete", null, - "AQAAAN4AAAAAAAAA3gAAAAAAAAACAAAAKCk", - "AQAAAAAAAAABAAAA3gAAAAAAAADgAAAAAAAAAP///////+9/" + "AQAAAM8AAAAAAAAAzwAAAAAAAAABAAAAcg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" ], [ 34, 1, "insert", { - "characters": "." + "characters": "r" }, - "AQAAAN4AAAAAAAAA3wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAAPC/" + "AQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" ], [ - 35, + 43, 1, "insert", { - "characters": "com" + "characters": "ion" }, - "AwAAAN8AAAAAAAAA4AAAAAAAAAAAAAAA4AAAAAAAAADhAAAAAAAAAAAAAADhAAAAAAAAAOIAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" + "BAAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACaAAAAAAAAAAEAAABlmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" ], [ - 36, - 1, - "insert", - { - "characters": "m" - }, - "AQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" - ], - [ - 37, - 1, - "insert", - { - "characters": "ent." - }, - "BAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 38, - 1, + 44, + 3, "left_delete", null, - "AQAAAOYAAAAAAAAA5gAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAA5wAAAAAAAADnAAAAAAAAAAAAAAAAAPC/" + "AwAAAJsAAAAAAAAAmwAAAAAAAAABAAAAbpoAAAAAAAAAmgAAAAAAAAABAAAAb5kAAAAAAAAAmQAAAAAAAAABAAAAaQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" ], [ - 39, + 45, 1, - "insert_snippet", + "insert", { - "contents": "($0)" + "characters": "e" }, - "AQAAAOYAAAAAAAAA6AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5gAAAAAAAADmAAAAAAAAAAAAAAAAAPC/" + "AQAAAJkAAAAAAAAAmgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" ], [ - 47, + 49, + 1, + "insert", + { + "characters": "e" + }, + "AgAAANMAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADUAAAAAAAAAAMAAABpb24", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAAAAAAAAANYAAAAAAAAAAAAAAAAA8L8" + ], + [ + 59, + 1, + "right_delete", + null, + "AQAAAJEAAAAAAAAAkQAAAAAAAAAPAAAALCBtaWdyYXRlPUZhbHNl", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACRAAAAAAAAAKAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 63, + 1, + "insert", + { + "characters": "\n" + }, + "AwAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACYAAAAAAAAAAAAAACYAAAAAAAAAJwAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAAAAckA" + ], + [ + 64, 1, "paste", null, - "AQAAAOcAAAAAAAAA8QAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5wAAAAAAAADnAAAAAAAAAAAAAAAAAPC/" - ], - [ - 65, - 1, - "insert", - { - "characters": "return" - }, - "BgAAANwAAAAAAAAA3QAAAAAAAAAAAAAA3QAAAAAAAADeAAAAAAAAAAAAAADeAAAAAAAAAN8AAAAAAAAAAAAAAN8AAAAAAAAA4AAAAAAAAAAAAAAA4AAAAAAAAADhAAAAAAAAAAAAAADhAAAAAAAAAOIAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3AAAAAAAAADcAAAAAAAAAAAAAAAAAPC/" + "AQAAAJwAAAAAAAAAqQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" ], [ 66, 1, "insert", { - "characters": " " + "characters": "if" }, - "AQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" + "AgAAAJwAAAAAAAAAnQAAAAAAAAAAAAAAnQAAAAAAAACeAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAAUkA" + ], + [ + 67, + 1, + "insert", + { + "characters": " db" + }, + "AwAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAAnwAAAAAAAACgAAAAAAAAAAAAAACgAAAAAAAAAKEAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" + ], + [ + 68, + 1, + "insert", + { + "characters": "_uri" + }, + "BAAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAChAAAAAAAAAKEAAAAAAAAAAAAAAAAA8L8" ], [ 69, 1, "insert", { - "characters": "#" + "characters": ".st" }, - "AQAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/gAAAAAAAAD+AAAAAAAAAAAAAAAAAEJA" + "AwAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 70, + 1, + "insert", + { + "characters": " " + }, + "AQAAAKgAAAAAAAAAqQAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" ], [ 72, 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAACvBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKVElNRV9GT1JNQVQgPSAiJVktJW0tJWQgJUg6JU06JVMiCgoKZGVmIGZpbmRfY29tbWVudF9ieV9pZChjb21tZW50X2lkKToKICAgIHJldHVybiBkYi5jb21tZW50KGNvbW1lbnRfaWQpCiAgICAjcmV0dXJuIENvbW1lbnQuZ2V0X2J5X2lkKGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5ub3RpZmllZCA9IGRhdGV0aW1lLm5vdygpLnN0cmZ0aW1lKFRJTUVfRk9STUFUKQogICAgY29tbWVudC5zYXZlKCkKCgpkZWYgcHVibGlzaF9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5wdWJsaXNoZWQgPSBkYXRldGltZS5ub3coKS5zdHJmdGltZShUSU1FX0ZPUk1BVCkKICAgIGNvbW1lbnQuc2F2ZSgpCgoKZGVmIGRlbGV0ZV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5kZWxldGVfaW5zdGFuY2UoKQoKCmRlZiBmaW5kX25vdF9ub3RpZmllZF9jb21tZW50cygpOgogICAgcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5ub3RpZmllZC5pc19udWxsKCkpCgoKZGVmIGZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbCgpKQoKCmRlZiBmaW5kX3B1Ymxpc2hlZF9jb21tZW50c19ieV91cmwodXJsKToKICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5vcmRlcl9ieSgrQ29tbWVudC5wdWJsaXNoZWQpCiAgICApCgoKZGVmIGNvdW50X3B1Ymxpc2hlZF9jb21tZW50cyh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBDb21tZW50LnNlbGVjdChDb21tZW50KQogICAgICAgIC53aGVyZSgoQ29tbWVudC51cmwgPT0gdXJsKSAmIChDb21tZW50LnB1Ymxpc2hlZC5pc19udWxsKEZhbHNlKSkpCiAgICAgICAgLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIENvbW1lbnQuc2VsZWN0KENvbW1lbnQpLndoZXJlKENvbW1lbnQucHVibGlzaGVkLmlzX251bGwoRmFsc2UpKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY3JlYXRlZCA9IGRhdGV0aW1lLm5vdygpLnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIpCiAgICBjb21tZW50ID0gQ29tbWVudCgKICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9Y3JlYXRlZCwKICAgICAgICBub3RpZmllZD1Ob25lLAogICAgICAgIHB1Ymxpc2hlZD1Ob25lLAogICAgKQogICAgY29tbWVudC5zYXZlKCkKICAgIHJldHVybiBjb21tZW50CgAAAAAAAAAAsAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": "arts" + }, + "BAAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" ], [ - 77, + 73, + 1, + "left_delete", + null, + "AQAAAKsAAAAAAAAAqwAAAAAAAAABAAAAcw", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 74, + 1, + "insert", + { + "characters": "w" + }, + "AQAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 75, + 3, + "left_delete", + null, + "AwAAAKsAAAAAAAAAqwAAAAAAAAABAAAAd6oAAAAAAAAAqgAAAAAAAAABAAAAdKkAAAAAAAAAqQAAAAAAAAABAAAAcg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" + ], + [ + 78, + 1, + "insert", + { + "characters": "\n\"" + }, + "AwAAAKoAAAAAAAAAqwAAAAAAAAAAAAAAqwAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACqAAAAAAAAAKoAAAAAAAAAAAAAAAAA8L8" + ], + [ + 79, + 1, + "left_delete", + null, + "AQAAALMAAAAAAAAAswAAAAAAAAABAAAAIg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" + ], + [ + 80, 1, "insert", { "characters": "#" }, - "AQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAAAA" + "AQAAALMAAAAAAAAAtAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAAAAAAAAALMAAAAAAAAAAAAAAAAA8L8" ], [ - 93, + 83, + 4, + "left_delete", + null, + "BAAAAKgAAAAAAAAAqAAAAAAAAAABAAAAYacAAAAAAAAApwAAAAAAAAABAAAAdKYAAAAAAAAApgAAAAAAAAABAAAAc6UAAAAAAAAApQAAAAAAAAABAAAALg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACpAAAAAAAAAKkAAAAAAAAAAAAAAAAA8L8" + ], + [ + 84, + 1, + "insert", + { + "characters": ".st" + }, + "AwAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 85, + 1, + "insert", + { + "characters": "art" + }, + "AwAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" + ], + [ + 86, + 6, + "left_delete", + null, + "BgAAAKoAAAAAAAAAqgAAAAAAAAABAAAAdKkAAAAAAAAAqQAAAAAAAAABAAAAcqgAAAAAAAAAqAAAAAAAAAABAAAAYacAAAAAAAAApwAAAAAAAAABAAAAdKYAAAAAAAAApgAAAAAAAAABAAAAc6UAAAAAAAAApQAAAAAAAAABAAAALg", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" + ], + [ + 87, + 1, + "insert_snippet", + { + "contents": "[$0]" + }, + "AQAAAKUAAAAAAAAApwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" + ], + [ + 94, + 1, + "insert", + { + "characters": ".startswith" + }, + "DAAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACmAAAAAAAAAAIAAABbXaYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAACuAAAAAAAAAK8AAAAAAAAAAAAAAK8AAAAAAAAAsAAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKcAAAAAAAAAAAAAAAAA8L8" + ], + [ + 95, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAALAAAAAAAAAAsgAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACwAAAAAAAAALAAAAAAAAAAAAAAAAAA8L8" + ], + [ + 102, + 1, + "insert_snippet", + { + "contents": "\"$0\"" + }, + "AQAAALEAAAAAAAAAswAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACxAAAAAAAAALEAAAAAAAAAAAAAAAAA8L8" + ], + [ + 103, + 1, + "paste", + null, + "AQAAALIAAAAAAAAAvwAAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAAAAAAAAALIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 106, + 1, + "cut", + null, + "AQAAAJ8AAAAAAAAAnwAAAAAAAAAiAAAAZGJfdXJpLnN0YXJ0c3dpdGgoInNxbGl0ZTptZW1vcnkiKQ", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADBAAAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" + ], + [ + 111, + 1, + "paste", + null, + "AgAAAOIAAAAAAAAABAEAAAAAAAAAAAAABAEAAAAAAAAEAQAAAAAAAAcAAABtaWdyYXRl", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpAAAAAAAAAOIAAAAAAAAAAAAAAAAA8L8" + ], + [ + 115, 1, "right_delete", null, - "AQAAAGQAAAAAAAAAZAAAAAAAAAABAAAAIw", - "AQAAAAAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAAAA" + "AQAAAJQAAAAAAAAAlAAAAAAAAAAkAAAAICAgICAgICBpZiAgCiAgICAgICAgI3NxbGl0ZTptZW1vcnkK", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACUAAAAAAAAALgAAAAAAAAAAAAAAAAAAAA" + ] + ] + }, + { + "file": "tests/test_api.py", + "settings": + { + "buffer_size": 1625, + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 3, + 1, + "insert", + { + "characters": "," + }, + "AQAAAH4CAAAAAAAAfwIAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB+AgAAAAAAAH4CAAAAAAAAAAAAAAAA8L8" ], [ - 99, + 4, 1, - "toggle_breakpoint", + "insert", + { + "characters": " " + }, + "AQAAAH8CAAAAAAAAgAIAAAAAAAAAAAAA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AgAAAAAAAH8CAAAAAAAAAAAAAAAA8L8" + ], + [ + 5, + 2, + "left_delete", null, - "AQAAANgAAAAAAAAAEgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" + "AgAAAH8CAAAAAAAAfwIAAAAAAAABAAAAIH4CAAAAAAAAfgIAAAAAAAABAAAALA", + "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAgAAAAAAAIACAAAAAAAAAAAAAAAA8L8" + ], + [ + 6, + 1, + "revert", + null, + "AgAAAAAAAAAAAAAAAAAAAAAAAAB3BgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCgppbXBvcnQgcHl0ZXN0Cgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYXRhYmFzZSwgZGFvCmZyb20gc3RhY29zeXMuaW50ZXJmYWNlIGltcG9ydCBhcGkKZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKCmRlZiBpbml0X3Rlc3RfZGIoKToKICAgIGMxID0gZGFvLmNyZWF0ZV9jb21tZW50KCIvc2l0ZTEiLCAiQm9iIiwgIi9ib2Iuc2l0ZSIsICIiLCAiY29tbWVudCAxIikKICAgIGMyID0gZGFvLmNyZWF0ZV9jb21tZW50KCIvc2l0ZTIiLCAiQmlsbCIsICIvYmlsbC5zaXRlIiwgIiIsICJjb21tZW50IDIiKQogICAgYzMgPSBkYW8uY3JlYXRlX2NvbW1lbnQoIi9zaXRlMyIsICJKYWNrIiwgIi9qYWNrLnNpdGUiLCAiIiwgImNvbW1lbnQgMyIpCiAgICBkYW8ucHVibGlzaF9jb21tZW50KGMxKQogICAgZGFvLnB1Ymxpc2hfY29tbWVudChjMykKICAgIGFzc2VydCBjMgoKCkBweXRlc3QuZml4dHVyZQpkZWYgY2xpZW50KCk6CiAgICBsb2dnZXIgPSBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXykKICAgIGRhdGFiYXNlLmNvbmZpZ3VyZSgic3FsaXRlOm1lbW9yeTovL2RiLnNxbGl0ZSIpCiAgICBpbml0X3Rlc3RfZGIoKQogICAgbG9nZ2VyLmluZm8oZiJzdGFydCBpbnRlcmZhY2Uge2FwaX0iKQogICAgcmV0dXJuIGFwcC50ZXN0X2NsaWVudCgpCgoKZGVmIHRlc3RfYXBpX3BpbmcoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL3BpbmciKQogICAgYXNzZXJ0IHJlc3AuZGF0YSA9PSBiIk9LIgoKCmRlZiB0ZXN0X2FwaV9jb3VudF9nbG9iYWwoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzL2NvdW50IikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBkWyJjb3VudCJdID09IDIKCgpkZWYgdGVzdF9hcGlfY291bnRfdXJsKGNsaWVudCk6CiAgICByZXNwID0gY2xpZW50LmdldCgiL2FwaS9jb21tZW50cy9jb3VudD91cmw9L3NpdGUxIikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBkWyJjb3VudCJdID09IDEKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzL2NvdW50P3VybD0vc2l0ZTIiKQogICAgZCA9IGpzb24ubG9hZHMocmVzcC5kYXRhKQogICAgYXNzZXJ0IGQgYW5kIGRbImNvdW50Il0gPT0gMAoKCmRlZiB0ZXN0X2FwaV9jb21tZW50KGNsaWVudCk6CiAgICByZXNwID0gY2xpZW50LmdldCgiL2FwaS9jb21tZW50cz91cmw9L3NpdGUxIikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBsZW4oZFsiZGF0YSJdKSA9PSAxCiAgICBjb21tZW50ID0gZFsiZGF0YSJdWzBdCiAgICBhc3NlcnQgY29tbWVudFsiYXV0aG9yIl0gPT0gIkJvYiIKICAgIGFzc2VydCBjb21tZW50WyJjb250ZW50Il0gPT0gImNvbW1lbnQgMSIKCgpkZWYgdGVzdF9hcGlfY29tbWVudF9ub3RfZm91bmQoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzP3VybD0vc2l0ZTIiKQogICAgZCA9IGpzb24ubG9hZHMocmVzcC5kYXRhKQogICAgYXNzZXJ0IGQgYW5kIGRbImRhdGEiXSA9PSBbXQoAAAAAAAAAAFkGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuwIAAAAAAAC7AgAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "tests/test_db.py", + "settings": + { + "buffer_size": 2740, + "encoding": "UTF-8", + "line_ending": "Unix" + }, + "undo_stack": + [ + [ + 7, + 1, + "insert", + { + "characters": " " + }, + "AQAAAHcAAAAAAAAAeAAAAAAAAAAAAAAA", + "AQAAAA" + ], + [ + 10, + 1, + "trim_trailing_white_space", + null, + "AQAAAHcAAAAAAAAAdwAAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 23, + 1, + "insert", + { + "characters": "\ndef" + }, + "BQAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADNAAAAAAAAAAEAAAAKzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzQAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" + ], + [ + 24, + 1, + "insert", + { + "characters": " test" + }, + "BQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA0QAAAAAAAADSAAAAAAAAAAAAAADSAAAAAAAAANMAAAAAAAAAAAAAANMAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADVAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0AAAAAAAAADQAAAAAAAAAAAAAAAAAPC/" + ], + [ + 25, + 1, + "insert", + { + "characters": "_findc" + }, + "BgAAANUAAAAAAAAA1gAAAAAAAAAAAAAA1gAAAAAAAADXAAAAAAAAAAAAAADXAAAAAAAAANgAAAAAAAAAAAAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1QAAAAAAAADVAAAAAAAAAAAAAAAAAPC/" + ], + [ + 26, + 1, + "left_delete", + null, + "AQAAANoAAAAAAAAA2gAAAAAAAAABAAAAYw", + "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" + ], + [ + 27, + 1, + "insert", + { + "characters": "_comment_" + }, + "CQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAADfAAAAAAAAAOAAAAAAAAAAAAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 28, + 1, + "insert", + { + "characters": "by_id" + }, + "BQAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA5wAAAAAAAADoAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" + ], + [ + 29, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAOgAAAAAAAAA6gAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6AAAAAAAAADoAAAAAAAAAAAAAAAAAPC/" + ], + [ + 32, + 1, + "insert", + { + "characters": "sert" + }, + "BAAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADrAAAAAAAAAAAAAADrAAAAAAAAAOwAAAAAAAAAAAAAAOwAAAAAAAAA7QAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" + ], + [ + 33, + 2, + "left_delete", + null, + "AgAAAOwAAAAAAAAA7AAAAAAAAAABAAAAdOsAAAAAAAAA6wAAAAAAAAABAAAAcg", + "AQAAAAAAAAABAAAA7QAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" + ], + [ + 34, + 1, + "insert", + { + "characters": "t" + }, + "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 35, + 1, + "insert_completion", + { + "completion": "setup_db", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "setup_db" + }, + "AgAAAOkAAAAAAAAA6QAAAAAAAAADAAAAc2V06QAAAAAAAADxAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7AAAAAAAAADsAAAAAAAAAAAAAAAAAPC/" + ], + [ + 37, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAPIAAAAAAAAA8wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA8gAAAAAAAADyAAAAAAAAAAAAAAAAAPC/" + ], + [ + 38, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAPMAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD4AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 48, + 1, + "insert", + { + "characters": "," + }, + "AgAAAPEAAAAAAAAA8gAAAAAAAAAAAAAA9QAAAAAAAAD1AAAAAAAAAAQAAAAgICAg", + "AQAAAAAAAAABAAAA8QAAAAAAAADxAAAAAAAAAAAAAAAAAPC/" + ], + [ + 49, + 1, + "insert", + { + "characters": " f" + }, + "AgAAAPIAAAAAAAAA8wAAAAAAAAAAAAAA8wAAAAAAAAD0AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA8gAAAAAAAADyAAAAAAAAAAAAAAAAAPC/" + ], + [ + 50, + 1, + "left_delete", + null, + "AQAAAPMAAAAAAAAA8wAAAAAAAAABAAAAZg", + "AQAAAAAAAAABAAAA9AAAAAAAAAD0AAAAAAAAAAAAAAAAAPC/" + ], + [ + 51, + 2, + "left_delete", + null, + "AgAAAPIAAAAAAAAA8gAAAAAAAAABAAAAIPEAAAAAAAAA8QAAAAAAAAABAAAALA", + "AQAAAAAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" + ], + [ + 53, + 1, + "reindent", + null, + "AQAAAPQAAAAAAAAA+AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA9AAAAAAAAAD0AAAAAAAAAAAAAAAAQHRA" + ], + [ + 54, + 1, + "insert", + { + "characters": "asse" + }, + "BAAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" + ], + [ + 55, + 1, + "insert", + { + "characters": "rt" + }, + "AgAAAPwAAAAAAAAA/QAAAAAAAAAAAAAA/QAAAAAAAAD+AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAPC/" + ], + [ + 56, + 1, + "insert", + { + "characters": " " + }, + "AQAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/gAAAAAAAAD+AAAAAAAAAAAAAAAAAPC/" + ], + [ + 58, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAEAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 61, + 1, + "insert", + { + "characters": " " + }, + "AgAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAAAQAAAAgICAg", + "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 66, + 1, + "paste", + null, + "AQAAAP8AAAAAAAAAEQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 67, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAABEBAAAAAAAAEwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" + ], + [ + 68, + 1, + "insert", + { + "characters": "1" + }, + "AQAAABIBAAAAAAAAEwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" + ], + [ + 73, + 1, + "insert", + { + "characters": "dao." + }, + "BAAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAABAQAAAAAAAAAAAAABAQAAAAAAAAIBAAAAAAAAAAAAAAIBAAAAAAAAAwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" + ], + [ + 75, + 1, + "trim_trailing_white_space", + null, + "AQAAABgBAAAAAAAAGAEAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAAwEAAAAAAAADAQAAAAAAAAAAAAAAAPC/" + ], + [ + 80, + 1, + "insert", + { + "characters": " is" + }, + "AwAAABgBAAAAAAAAGQEAAAAAAAAAAAAAGQEAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGAEAAAAAAAAYAQAAAAAAAAAAAAAAAPC/" + ], + [ + 81, + 1, + "insert", + { + "characters": " None" + }, + "BQAAABsBAAAAAAAAHAEAAAAAAAAAAAAAHAEAAAAAAAAdAQAAAAAAAAAAAAAdAQAAAAAAAB4BAAAAAAAAAAAAAB4BAAAAAAAAHwEAAAAAAAAAAAAAHwEAAAAAAAAgAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGwEAAAAAAAAbAQAAAAAAAAAAAAAAAPC/" + ], + [ + 84, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAACABAAAAAAAAIQEAAAAAAAAAAAAAIQEAAAAAAAAlAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAPC/" + ], + [ + 104, + 1, + "paste", + null, + "AQAAACUBAAAAAAAAYgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJQEAAAAAAAAlAQAAAAAAAAAAAAAAAPC/" + ], + [ + 106, + 1, + "insert", + { + "characters": "\nasser" + }, + "BwAAAGIBAAAAAAAAYwEAAAAAAAAAAAAAYwEAAAAAAABnAQAAAAAAAAAAAABnAQAAAAAAAGgBAAAAAAAAAAAAAGgBAAAAAAAAaQEAAAAAAAAAAAAAaQEAAAAAAABqAQAAAAAAAAAAAABqAQAAAAAAAGsBAAAAAAAAAAAAAGsBAAAAAAAAbAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYgEAAAAAAABiAQAAAAAAAAAAAAAAAPC/" + ], + [ + 107, + 1, + "insert", + { + "characters": "t" + }, + "AQAAAGwBAAAAAAAAbQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbAEAAAAAAABsAQAAAAAAAAAAAAAAAPC/" + ], + [ + 108, + 1, + "insert", + { + "characters": " " + }, + "AQAAAG0BAAAAAAAAbgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbQEAAAAAAABtAQAAAAAAAAAAAAAAAPC/" ], [ 109, 1, "insert", { - "characters": "()" + "characters": "c1." }, - "AgAAAB8BAAAAAAAAIAEAAAAAAAAAAAAAIAEAAAAAAAAhAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHwEAAAAAAAAfAQAAAAAAAAAAAAAAAPC/" + "AwAAAG4BAAAAAAAAbwEAAAAAAAAAAAAAbwEAAAAAAABwAQAAAAAAAAAAAABwAQAAAAAAAHEBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" + ], + [ + 110, + 1, + "insert", + { + "characters": "id" + }, + "AgAAAHEBAAAAAAAAcgEAAAAAAAAAAAAAcgEAAAAAAABzAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcQEAAAAAAABxAQAAAAAAAAAAAAAAAPC/" + ], + [ + 111, + 1, + "insert", + { + "characters": " is" + }, + "AwAAAHMBAAAAAAAAdAEAAAAAAAAAAAAAdAEAAAAAAAB1AQAAAAAAAAAAAAB1AQAAAAAAAHYBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcwEAAAAAAABzAQAAAAAAAAAAAAAAAPC/" + ], + [ + 112, + 1, + "insert", + { + "characters": " not" + }, + "BAAAAHYBAAAAAAAAdwEAAAAAAAAAAAAAdwEAAAAAAAB4AQAAAAAAAAAAAAB4AQAAAAAAAHkBAAAAAAAAAAAAAHkBAAAAAAAAegEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgEAAAAAAAB2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 113, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAHoBAAAAAAAAewEAAAAAAAAAAAAAewEAAAAAAAB8AQAAAAAAAAAAAAB8AQAAAAAAAH0BAAAAAAAAAAAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAegEAAAAAAAB6AQAAAAAAAAAAAAAAAPC/" ], [ 117, 1, - "toggle_breakpoint", + "reindent", null, - "AQAAANgAAAAAAAAA2AAAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDg4MzY3Y2ZlIC8vCg", - "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" + "AQAAAIABAAAAAAAAhAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAgG9A" ], [ - 133, + 120, 1, "insert", { - "characters": "\n" + "characters": "c2" }, - "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACFAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + "AgAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAEJA" ], [ - 134, + 121, + 2, + "left_delete", + null, + "AgAAAIUBAAAAAAAAhQEAAAAAAAABAAAAMoQBAAAAAAAAhAEAAAAAAAABAAAAYw", + "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" + ], + [ + 122, 1, "insert", { - "characters": "returb" + "characters": "rec" }, - "BgAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAACHAgAAAAAAAIgCAAAAAAAAAAAAAIgCAAAAAAAAiQIAAAAAAAAAAAAAiQIAAAAAAACKAgAAAAAAAAAAAACKAgAAAAAAAIsCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQIAAAAAAACFAgAAAAAAAAAAAAAAAPC/" + "AwAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAACGAQAAAAAAAIcBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" ], [ - 135, + 123, + 3, + "left_delete", + null, + "AwAAAIYBAAAAAAAAhgEAAAAAAAABAAAAY4UBAAAAAAAAhQEAAAAAAAABAAAAZYQBAAAAAAAAhAEAAAAAAAABAAAAcg", + "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" + ], + [ + 124, + 1, + "insert", + { + "characters": "find_c1" + }, + "BwAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAACGAQAAAAAAAIcBAAAAAAAAAAAAAIcBAAAAAAAAiAEAAAAAAAAAAAAAiAEAAAAAAACJAQAAAAAAAAAAAACJAQAAAAAAAIoBAAAAAAAAAAAAAIoBAAAAAAAAiwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" + ], + [ + 125, + 1, + "insert", + { + "characters": " " + }, + "AgAAAIsBAAAAAAAAjAEAAAAAAAAAAAAAjAEAAAAAAACNAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiwEAAAAAAACLAQAAAAAAAAAAAAAAAPC/" + ], + [ + 126, + 1, + "left_delete", + null, + "AQAAAIwBAAAAAAAAjAEAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAAjQEAAAAAAACNAQAAAAAAAAAAAAAAAPC/" + ], + [ + 127, + 1, + "insert", + { + "characters": "=" + }, + "AQAAAIwBAAAAAAAAjQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjAEAAAAAAACMAQAAAAAAAAAAAAAAAPC/" + ], + [ + 128, 1, "insert", { "characters": " " }, - "AQAAAIsCAAAAAAAAjAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + "AQAAAI0BAAAAAAAAjgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQEAAAAAAACNAQAAAAAAAAAAAAAAAPC/" ], [ 136, - 2, - "left_delete", + 1, + "paste", null, - "AgAAAIsCAAAAAAAAiwIAAAAAAAABAAAAIIoCAAAAAAAAigIAAAAAAAABAAAAYg", - "AQAAAAAAAAABAAAAjAIAAAAAAACMAgAAAAAAAAAAAAAAAPC/" - ], - [ - 137, - 1, - "insert", - { - "characters": "n" - }, - "AQAAAIoCAAAAAAAAiwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAigIAAAAAAACKAgAAAAAAAAAAAAAAAPC/" - ], - [ - 138, - 1, - "insert", - { - "characters": " db" - }, - "AwAAAIsCAAAAAAAAjAIAAAAAAAAAAAAAjAIAAAAAAACNAgAAAAAAAAAAAACNAgAAAAAAAI4CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" + "AQAAAI4BAAAAAAAApwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjgEAAAAAAACOAQAAAAAAAAAAAAAAQHRA" ], [ 139, 1, - "insert_snippet", + "insert", { - "contents": "($0)" + "characters": "c1.id" }, - "AQAAAI4CAAAAAAAAkAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjgIAAAAAAACOAgAAAAAAAAAAAAAAAPC/" + "BgAAAKUBAAAAAAAApgEAAAAAAAAAAAAApgEAAAAAAACmAQAAAAAAAAEAAAAxpgEAAAAAAACnAQAAAAAAAAAAAACnAQAAAAAAAKgBAAAAAAAAAAAAAKgBAAAAAAAAqQEAAAAAAAAAAAAAqQEAAAAAAACqAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAApgEAAAAAAAClAQAAAAAAAAAAAAAAAPC/" ], [ 141, 1, - "insert_snippet", + "insert", { - "contents": "($0)" + "characters": "\nassert" }, - "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAIAAAAAAACQAgAAAAAAAAAAAAAAAPC/" + "CAAAAKsBAAAAAAAArAEAAAAAAAAAAAAArAEAAAAAAACwAQAAAAAAAAAAAACwAQAAAAAAALEBAAAAAAAAAAAAALEBAAAAAAAAsgEAAAAAAAAAAAAAsgEAAAAAAACzAQAAAAAAAAAAAACzAQAAAAAAALQBAAAAAAAAAAAAALQBAAAAAAAAtQEAAAAAAAAAAAAAtQEAAAAAAAC2AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqwEAAAAAAACrAQAAAAAAAAAAAAAAAPC/" + ], + [ + 142, + 1, + "insert", + { + "characters": " " + }, + "AQAAALYBAAAAAAAAtwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtgEAAAAAAAC2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 143, + 1, + "insert", + { + "characters": "find" + }, + "BAAAALcBAAAAAAAAuAEAAAAAAAAAAAAAuAEAAAAAAAC5AQAAAAAAAAAAAAC5AQAAAAAAALoBAAAAAAAAAAAAALoBAAAAAAAAuwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtwEAAAAAAAC3AQAAAAAAAAAAAAAAAPC/" + ], + [ + 144, + 1, + "insert", + { + "characters": "_c1" + }, + "AwAAALsBAAAAAAAAvAEAAAAAAAAAAAAAvAEAAAAAAAC9AQAAAAAAAAAAAAC9AQAAAAAAAL4BAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAuwEAAAAAAAC7AQAAAAAAAAAAAAAAAPC/" + ], + [ + 145, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"find_c1\",\"position\":{\"line\":17,\"character\":18},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"find_c1\",\"sortText\":\"09.9999.find_c1\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "find_c1" + }, + "AgAAALcBAAAAAAAAtwEAAAAAAAAHAAAAZmluZF9jMbcBAAAAAAAAvgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" + ], + [ + 146, + 1, + "insert", + { + "characters": "\n\n" + }, + "BQAAAL4BAAAAAAAAvwEAAAAAAAAAAAAAvwEAAAAAAADDAQAAAAAAAAAAAADDAQAAAAAAAMQBAAAAAAAAAAAAAMQBAAAAAAAAyAEAAAAAAAAAAAAAvwEAAAAAAAC/AQAAAAAAAAQAAAAgICAg", + "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" + ], + [ + 148, + 1, + "reindent", + null, + "AQAAAL8BAAAAAAAAwwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAEJA" + ], + [ + 149, + 1, + "insert", + { + "characters": "assert" + }, + "BwAAAMMBAAAAAAAAxAEAAAAAAAAAAAAAxQEAAAAAAADFAQAAAAAAAAQAAAAgICAgxAEAAAAAAADFAQAAAAAAAAAAAADFAQAAAAAAAMYBAAAAAAAAAAAAAMYBAAAAAAAAxwEAAAAAAAAAAAAAxwEAAAAAAADIAQAAAAAAAAAAAADIAQAAAAAAAMkBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwwEAAAAAAADDAQAAAAAAAAAAAAAAAPC/" ], [ 150, 1, - "right_delete", - null, - "AQAAAIUCAAAAAAAAhQIAAAAAAAANAAAAcmV0dXJuIGRiKCkoKQ", - "AQAAAAAAAAABAAAAkgIAAAAAAACFAgAAAAAAAAAAAAAAAEJA" + "insert", + { + "characters": " find_" + }, + "BgAAAMkBAAAAAAAAygEAAAAAAAAAAAAAygEAAAAAAADLAQAAAAAAAAAAAADLAQAAAAAAAMwBAAAAAAAAAAAAAMwBAAAAAAAAzQEAAAAAAAAAAAAAzQEAAAAAAADOAQAAAAAAAAAAAADOAQAAAAAAAM8BAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAyQEAAAAAAADJAQAAAAAAAAAAAAAAAPC/" ], [ - 152, + 157, 1, - "left_delete", - null, - "AQAAAIACAAAAAAAAgAIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAgQIAAAAAAACBAgAAAAAAAAAAAAAAAAAA" + "insert", + { + "characters": "c1.i" + }, + "BAAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA0AEAAAAAAADRAQAAAAAAAAAAAADRAQAAAAAAANIBAAAAAAAAAAAAANIBAAAAAAAA0wEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" ], [ - 154, + 158, 1, - "trim_trailing_white_space", - null, - "AQAAAIACAAAAAAAAgAIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAgAIAAAAAAACAAgAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": "d" + }, + "AQAAANMBAAAAAAAA1AEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0wEAAAAAAADTAQAAAAAAAAAAAAAAAPC/" + ], + [ + 159, + 1, + "insert", + { + "characters": " " + }, + "AQAAANQBAAAAAAAA1QEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1AEAAAAAAADUAQAAAAAAAAAAAAAAAPC/" ], [ 160, - 1, - "right_delete", + 3, + "left_delete", null, - "AQAAAAABAAAAAAAAAAEAAAAAAAAmAAAAIyByZXR1cm4gQ29tbWVudC5nZXRfYnlfaWQoY29tbWVudF9pZCk", - "AQAAAAAAAAABAAAAJgEAAAAAAAAAAQAAAAAAAAAAAAAAAEJA" + "AwAAANQBAAAAAAAA1AEAAAAAAAABAAAAINMBAAAAAAAA0wEAAAAAAAABAAAAZNIBAAAAAAAA0gEAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" ], [ 162, 1, - "left_delete", - null, - "AQAAAPsAAAAAAAAA+wAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAAAA" + "insert", + { + "characters": "id" + }, + "AgAAANIBAAAAAAAA0wEAAAAAAAAAAAAA0wEAAAAAAADUAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0gEAAAAAAADSAQAAAAAAAAAAAAAAAPC/" + ], + [ + 163, + 1, + "insert", + { + "characters": " =" + }, + "AgAAANQBAAAAAAAA1QEAAAAAAAAAAAAA1QEAAAAAAADWAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1AEAAAAAAADUAQAAAAAAAAAAAAAAAPC/" ], [ 164, 1, - "trim_trailing_white_space", - null, - "AQAAAPsAAAAAAAAA+wAAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAA+wAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" + "insert", + { + "characters": " c1." + }, + "BAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA1wEAAAAAAADYAQAAAAAAAAAAAADYAQAAAAAAANkBAAAAAAAAAAAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1gEAAAAAAADWAQAAAAAAAAAAAAAAAPC/" ], [ - 193, + 165, 1, - "right_delete", - null, - "AQAAAEkBAAAAAAAASQEAAAAAAAAWAAAALnN0cmZ0aW1lKFRJTUVfRk9STUFUKQ", - "AQAAAAAAAAABAAAASQEAAAAAAABfAQAAAAAAAP///////+9/" + "insert", + { + "characters": "id" + }, + "AgAAANoBAAAAAAAA2wEAAAAAAAAAAAAA2wEAAAAAAADcAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gEAAAAAAADaAQAAAAAAAAAAAAAAAPC/" ], [ - 196, + 166, 1, "insert", { "characters": "\n" }, - "AgAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAA0HRA" + "AgAAANwBAAAAAAAA3QEAAAAAAAAAAAAA3QEAAAAAAADhAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3AEAAAAAAADcAQAAAAAAAAAAAAAAAPC/" ], [ - 197, + 168, + 1, + "trim_trailing_white_space", + null, + "AQAAAN0BAAAAAAAA3QEAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAA4QEAAAAAAADhAQAAAAAAAAAAAAAAAPC/" + ], + [ + 172, 1, "insert", { - "characters": "db" + "characters": "=" }, - "AgAAAE4BAAAAAAAATwEAAAAAAAAAAAAATwEAAAAAAABQAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgEAAAAAAABOAQAAAAAAAAAAAAAAAPC/" + "AQAAANUBAAAAAAAA1gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" ], [ - 198, + 178, + 1, + "insert", + { + "characters": "\ndef" + }, + "BAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAA4GlA" + ], + [ + 179, + 1, + "insert", + { + "characters": " " + }, + "AQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0AAAAAAAAADQAAAAAAAAAAAAAAAAAPC/" + ], + [ + 180, + 1, + "insert", + { + "characters": "is_" + }, + "AwAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADTAAAAAAAAAAAAAADTAAAAAAAAANQAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0QAAAAAAAADRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 181, + 3, + "left_delete", + null, + "AwAAANMAAAAAAAAA0wAAAAAAAAABAAAAX9IAAAAAAAAA0gAAAAAAAAABAAAAc9EAAAAAAAAA0QAAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAA1AAAAAAAAADUAAAAAAAAAAAAAAAAAPC/" + ], + [ + 182, + 1, + "insert", + { + "characters": "equals_" + }, + "BwAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADTAAAAAAAAAAAAAADTAAAAAAAAANQAAAAAAAAAAAAAANQAAAAAAAAA1QAAAAAAAAAAAAAA1QAAAAAAAADWAAAAAAAAAAAAAADWAAAAAAAAANcAAAAAAAAAAAAAANcAAAAAAAAA2AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0QAAAAAAAADRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 183, + 1, + "insert", + { + "characters": "oco" + }, + "AwAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" + ], + [ + 184, + 3, + "left_delete", + null, + "AwAAANoAAAAAAAAA2gAAAAAAAAABAAAAb9kAAAAAAAAA2QAAAAAAAAABAAAAY9gAAAAAAAAA2AAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" + ], + [ + 185, + 1, + "insert", + { + "characters": "coù" + }, + "AwAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" + ], + [ + 186, + 1, + "left_delete", + null, + "AQAAANoAAAAAAAAA2gAAAAAAAAACAAAAw7k", + "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" + ], + [ + 187, + 1, + "insert", + { + "characters": "mment" + }, + "BQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 188, 1, "insert_snippet", { "contents": "($0)" }, - "AQAAAFABAAAAAAAAUgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUAEAAAAAAABQAQAAAAAAAAAAAAAAAPC/" + "AQAAAN8AAAAAAAAA4QAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" ], [ - 200, + 189, 1, "insert", { - "characters": ".inser" + "characters": "c" }, - "BgAAAFIBAAAAAAAAUwEAAAAAAAAAAAAAUwEAAAAAAABUAQAAAAAAAAAAAABUAQAAAAAAAFUBAAAAAAAAAAAAAFUBAAAAAAAAVgEAAAAAAAAAAAAAVgEAAAAAAABXAQAAAAAAAAAAAABXAQAAAAAAAFgBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" + "AQAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" ], [ - 201, - 5, - "left_delete", - null, - "BQAAAFcBAAAAAAAAVwEAAAAAAAABAAAAclYBAAAAAAAAVgEAAAAAAAABAAAAZVUBAAAAAAAAVQEAAAAAAAABAAAAc1QBAAAAAAAAVAEAAAAAAAABAAAAblMBAAAAAAAAUwEAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAAWAEAAAAAAABYAQAAAAAAAAAAAAAAAPC/" - ], - [ - 202, + 190, 1, "insert", { - "characters": "comment.i" + "characters": ":" }, - "CQAAAFMBAAAAAAAAVAEAAAAAAAAAAAAAVAEAAAAAAABVAQAAAAAAAAAAAABVAQAAAAAAAFYBAAAAAAAAAAAAAFYBAAAAAAAAVwEAAAAAAAAAAAAAVwEAAAAAAABYAQAAAAAAAAAAAABYAQAAAAAAAFkBAAAAAAAAAAAAAFkBAAAAAAAAWgEAAAAAAAAAAAAAWgEAAAAAAABbAQAAAAAAAAAAAABbAQAAAAAAAFwBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUwEAAAAAAABTAQAAAAAAAAAAAAAAAPC/" + "AQAAAOEAAAAAAAAA4gAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4QAAAAAAAADhAAAAAAAAAAAAAAAAAPC/" ], [ - 203, + 191, 1, "insert", { - "characters": "ns" + "characters": " c" }, - "AgAAAFwBAAAAAAAAXQEAAAAAAAAAAAAAXQEAAAAAAABeAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXAEAAAAAAABcAQAAAAAAAAAAAAAAAPC/" + "AgAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA4wAAAAAAAADkAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" ], [ - 204, - 1, - "insert", - { - "characters": "ert-" - }, - "BAAAAF4BAAAAAAAAXwEAAAAAAAAAAAAAXwEAAAAAAABgAQAAAAAAAAAAAABgAQAAAAAAAGEBAAAAAAAAAAAAAGEBAAAAAAAAYgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXgEAAAAAAABeAQAAAAAAAAAAAAAAAPC/" - ], - [ - 205, + 192, 1, "left_delete", null, - "AQAAAGEBAAAAAAAAYQEAAAAAAAABAAAALQ", - "AQAAAAAAAAABAAAAYgEAAAAAAABiAQAAAAAAAAAAAAAAAPC/" + "AQAAAOMAAAAAAAAA4wAAAAAAAAABAAAAYw", + "AQAAAAAAAAABAAAA5AAAAAAAAADkAAAAAAAAAAAAAAAAAPC/" + ], + [ + 193, + 1, + "insert", + { + "characters": "Comment," + }, + "CAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA5wAAAAAAAADoAAAAAAAAAAAAAADoAAAAAAAAAOkAAAAAAAAAAAAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADrAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" + ], + [ + 194, + 1, + "insert", + { + "characters": " " + }, + "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" ], [ 206, 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAGEBAAAAAAAAYwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYQEAAAAAAABhAQAAAAAAAAAAAAAAAPC/" - ], - [ - 211, - 1, - "paste", + "right_delete", null, - "AQAAAEgAAAAAAAAAaQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASAAAAAAAAABIAAAAAAAAAAAAAAAAAPC/" + "AQAAAOEAAAAAAAAA4QAAAAAAAAAJAAAAOiBDb21tZW50", + "AQAAAAAAAAABAAAA6gAAAAAAAADhAAAAAAAAAAAAAAAAAPC/" ], [ - 213, + 208, 1, "insert", { - "characters": "asd" + "characters": "c1," }, - "BAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABhAAAAAAAAAAkAAABkYXRhY2xhc3NhAAAAAAAAAGIAAAAAAAAAAAAAAGIAAAAAAAAAYwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaQAAAAAAAABgAAAAAAAAAAAAAAAAAPC/" + "BAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADhAAAAAAAAAAEAAABj4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4QAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" ], [ - 214, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"05.0000.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":3,\"character\":27},\"symbolLabel\":\"asdict\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "asdict" - }, - "AgAAAGAAAAAAAAAAYAAAAAAAAAADAAAAYXNkYAAAAAAAAABmAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 217, + 209, 1, "insert", { - "characters": "asd" + "characters": " c2" }, - "AwAAAIABAAAAAAAAgQEAAAAAAAAAAAAAgQEAAAAAAACCAQAAAAAAAAAAAACCAQAAAAAAAIMBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAAPC/" + "AwAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" + ], + [ + 210, + 2, + "right_delete", + null, + "AgAAAOYAAAAAAAAA5gAAAAAAAAABAAAALOYAAAAAAAAA5gAAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAA5gAAAAAAAADmAAAAAAAAAAAAAAAAAPC/" ], [ 218, 1, - "insert_completion", + "insert", { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"05.0000.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":16,\"character\":27},\"symbolLabel\":\"asdict\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "asdict" + "characters": "com" }, - "AgAAAIABAAAAAAAAgAEAAAAAAAADAAAAYXNkgAEAAAAAAACGAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwEAAAAAAACDAQAAAAAAAAAAAAAAAPC/" + "BAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADhAAAAAAAAAAIAAABjMeEAAAAAAAAA4gAAAAAAAAAAAAAA4gAAAAAAAADjAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" ], [ 219, 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAIYBAAAAAAAAiAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" + "left_delete", + null, + "AQAAAOIAAAAAAAAA4gAAAAAAAAABAAAAbQ", + "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" ], [ 220, 1, "insert", { - "characters": "comment" + "characters": "mment" }, - "BwAAAIcBAAAAAAAAiAEAAAAAAAAAAAAAiAEAAAAAAACJAQAAAAAAAAAAAACJAQAAAAAAAIoBAAAAAAAAAAAAAIoBAAAAAAAAiwEAAAAAAAAAAAAAiwEAAAAAAACMAQAAAAAAAAAAAACMAQAAAAAAAI0BAAAAAAAAAAAAAI0BAAAAAAAAjgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" - ], - [ - 225, - 1, - "right_delete", - null, - "AQAAAJUBAAAAAAAAlQEAAAAAAAAOAAAAY29tbWVudC5zYXZlKCk", - "AQAAAAAAAAABAAAAowEAAAAAAACVAQAAAAAAAAAAAAAAAEJA" + "BQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA4wAAAAAAAADkAAAAAAAAAAAAAADkAAAAAAAAAOUAAAAAAAAAAAAAAOUAAAAAAAAA5gAAAAAAAAAAAAAA5gAAAAAAAADnAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" ], [ 227, 1, - "left_delete", - null, - "AQAAAJABAAAAAAAAkAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAkQEAAAAAAACRAQAAAAAAAAAAAAAAAAAA" + "insert", + { + "characters": "other" + }, + "BgAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADqAAAAAAAAAAIAAABjMuoAAAAAAAAA6wAAAAAAAAAAAAAA6wAAAAAAAADsAAAAAAAAAAAAAADsAAAAAAAAAO0AAAAAAAAAAAAAAO0AAAAAAAAA7gAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6QAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" + ], + [ + 229, + 1, + "insert", + { + "characters": ":" + }, + "AQAAAO8AAAAAAAAA8AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7wAAAAAAAADvAAAAAAAAAAAAAAAAAPC/" ], [ 230, 1, - "trim_trailing_white_space", - null, - "AQAAAJABAAAAAAAAkAEAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAlQEAAAAAAACVAQAAAAAAAAAAAAAAgHZA" - ], - [ - 235, - 1, - "right_delete", - null, - "AQAAAOABAAAAAAAA4AEAAAAAAAAWAAAALnN0cmZ0aW1lKFRJTUVfRk9STUFUKQ", - "AQAAAAAAAAABAAAA4AEAAAAAAAD2AQAAAAAAAP///////+9/" - ], - [ - 239, - 1, - "right_delete", - null, - "AQAAAK4AAAAAAAAArgAAAAAAAAAhAAAAVElNRV9GT1JNQVQgPSAiJVktJW0tJWQgJUg6JU06JVMi", - "AQAAAAAAAAABAAAAzwAAAAAAAACuAAAAAAAAAAAAAAAAAAAA" - ], - [ - 240, - 2, - "left_delete", - null, - "AgAAAK0AAAAAAAAArQAAAAAAAAABAAAACqwAAAAAAAAArAAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAArgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 251, - 1, - "paste", - null, - "AgAAAMIBAAAAAAAA5gEAAAAAAAAAAAAA5gEAAAAAAADmAQAAAAAAAA4AAABjb21tZW50LnNhdmUoKQ", - "AQAAAAAAAAABAAAAwgEAAAAAAADQAQAAAAAAAAAAAAAAAPC/" - ], - [ - 261, - 1, - "insert", - { - "characters": "\ndb" - }, - "BAAAAA4CAAAAAAAADwIAAAAAAAAAAAAADwIAAAAAAAATAgAAAAAAAAAAAAATAgAAAAAAABQCAAAAAAAAAAAAABQCAAAAAAAAFQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADgIAAAAAAAAOAgAAAAAAAAAAAAAAAPC/" - ], - [ - 262, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABUCAAAAAAAAFwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFQIAAAAAAAAVAgAAAAAAAAAAAAAAAPC/" - ], - [ - 264, - 1, - "insert", - { - "characters": "." - }, - "AQAAABcCAAAAAAAAGAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFwIAAAAAAAAXAgAAAAAAAAAAAAAAAPC/" - ], - [ - 265, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABgCAAAAAAAAGgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" - ], - [ - 266, - 1, - "run_macro_file", - { - "file": "res://Packages/Default/Delete Left Right.sublime-macro" - }, - "AgAAABgCAAAAAAAAGAIAAAAAAAABAAAAKBgCAAAAAAAAGAIAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAAGQIAAAAAAAAZAgAAAAAAAAAAAAAAAPC/" - ], - [ - 267, - 1, - "left_delete", - null, - "AQAAABcCAAAAAAAAFwIAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" - ], - [ - 268, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABcCAAAAAAAAGQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFwIAAAAAAAAXAgAAAAAAAAAAAAAAAPC/" - ], - [ - 269, - 1, - "insert", - { - "characters": "db" - }, - "AgAAABgCAAAAAAAAGQIAAAAAAAAAAAAAGQIAAAAAAAAaAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGAIAAAAAAAAYAgAAAAAAAAAAAAAAAPC/" - ], - [ - 270, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABoCAAAAAAAAHAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGgIAAAAAAAAaAgAAAAAAAAAAAAAAAPC/" - ], - [ - 272, - 1, - "insert", - { - "characters": ".comment." - }, - "CQAAABwCAAAAAAAAHQIAAAAAAAAAAAAAHQIAAAAAAAAeAgAAAAAAAAAAAAAeAgAAAAAAAB8CAAAAAAAAAAAAAB8CAAAAAAAAIAIAAAAAAAAAAAAAIAIAAAAAAAAhAgAAAAAAAAAAAAAhAgAAAAAAACICAAAAAAAAAAAAACICAAAAAAAAIwIAAAAAAAAAAAAAIwIAAAAAAAAkAgAAAAAAAAAAAAAkAgAAAAAAACUCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHAIAAAAAAAAcAgAAAAAAAAAAAAAAAPC/" - ], - [ - 273, - 1, - "insert", - { - "characters": "id" - }, - "AgAAACUCAAAAAAAAJgIAAAAAAAAAAAAAJgIAAAAAAAAnAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJQIAAAAAAAAlAgAAAAAAAAAAAAAAAPC/" - ], - [ - 275, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAACcCAAAAAAAAKAIAAAAAAAAAAAAAKAIAAAAAAAApAgAAAAAAAAAAAAApAgAAAAAAACoCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJwIAAAAAAAAnAgAAAAAAAAAAAAAAAPC/" - ], - [ - 276, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAACoCAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAAsAgAAAAAAAAAAAAAsAgAAAAAAAC0CAAAAAAAAAAAAAC0CAAAAAAAALgIAAAAAAAAAAAAALgIAAAAAAAAvAgAAAAAAAAAAAAAvAgAAAAAAADACAAAAAAAAAAAAADACAAAAAAAAMQIAAAAAAAAAAAAAMQIAAAAAAAAyAgAAAAAAAAAAAAAyAgAAAAAAADMCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKgIAAAAAAAAqAgAAAAAAAAAAAAAAAPC/" - ], - [ - 277, - 1, - "insert", - { - "characters": "id" - }, - "AgAAADMCAAAAAAAANAIAAAAAAAAAAAAANAIAAAAAAAA1AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMwIAAAAAAAAzAgAAAAAAAAAAAAAAAPC/" - ], - [ - 278, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.id\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":23,\"character\":38},\"symbolLabel\":\"id\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"id\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "id" - }, - "AgAAADMCAAAAAAAAMwIAAAAAAAACAAAAaWQzAgAAAAAAADUCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAPC/" - ], - [ - 282, - 1, - "insert", - { - "characters": ".de" - }, - "AwAAADYCAAAAAAAANwIAAAAAAAAAAAAANwIAAAAAAAA4AgAAAAAAAAAAAAA4AgAAAAAAADkCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANgIAAAAAAAA2AgAAAAAAAAAAAAAA8HVA" - ], - [ - 283, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.delete\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":23,\"character\":42},\"symbolLabel\":\"delete\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"delete\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "delete" - }, - "AgAAADcCAAAAAAAANwIAAAAAAAACAAAAZGU3AgAAAAAAAD0CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOQIAAAAAAAA5AgAAAAAAAAAAAAAAAPC/" - ], - [ - 284, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAD0CAAAAAAAAPwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPQIAAAAAAAA9AgAAAAAAAAAAAAAAAPC/" - ], - [ - 287, - 1, - "insert", - { - "characters": "_" - }, - "AQAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" - ], - [ - 295, - 1, - "left_delete", - null, - "AQAAADcCAAAAAAAANwIAAAAAAAABAAAAXw", - "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAEHdA" - ], - [ - 298, - 1, - "right_delete", - null, - "AQAAAEQCAAAAAAAARAIAAAAAAAAZAAAAY29tbWVudC5kZWxldGVfaW5zdGFuY2UoKQ", - "AQAAAAAAAAABAAAAXQIAAAAAAABEAgAAAAAAAAAAAAAAAEJA" - ], - [ - 300, - 1, - "left_delete", - null, - "AQAAAD8CAAAAAAAAPwIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAQAIAAAAAAABAAgAAAAAAAAAAAAAAAAAA" - ], - [ - 302, - 1, - "trim_trailing_white_space", - null, - "AQAAAD8CAAAAAAAAPwIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAAAPC/" - ], - [ - 309, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAGMCAAAAAAAAZAIAAAAAAAAAAAAAZAIAAAAAAABoAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwIAAAAAAABjAgAAAAAAAAAAAAAAAPC/" - ], - [ - 310, - 1, - "paste", - null, - "AQAAAGgCAAAAAAAAiwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaAIAAAAAAABoAgAAAAAAAAAAAAAAAPC/" - ], - [ - 312, - 1, - "insert", - { - "characters": "return" - }, - "BgAAAGgCAAAAAAAAaQIAAAAAAAAAAAAAaQIAAAAAAABqAgAAAAAAAAAAAABqAgAAAAAAAGsCAAAAAAAAAAAAAGsCAAAAAAAAbAIAAAAAAAAAAAAAbAIAAAAAAABtAgAAAAAAAAAAAABtAgAAAAAAAG4CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaAIAAAAAAABoAgAAAAAAAAAAAAAAAEJA" - ], - [ - 313, - 1, - "insert", - { - "characters": " " - }, - "AQAAAG4CAAAAAAAAbwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbgIAAAAAAABuAgAAAAAAAAAAAAAAAPC/" - ], - [ - 319, - 1, - "insert", - { - "characters": "notified" - }, - "CQAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACCAgAAAAAAAAIAAABpZIICAAAAAAAAgwIAAAAAAAAAAAAAgwIAAAAAAACEAgAAAAAAAAAAAACEAgAAAAAAAIUCAAAAAAAAAAAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAACHAgAAAAAAAIgCAAAAAAAAAAAAAIgCAAAAAAAAiQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQIAAAAAAACDAgAAAAAAAAAAAAAAAPC/" - ], - [ - 322, - 1, - "insert", - { - "characters": "None" - }, - "BQAAAI0CAAAAAAAAjgIAAAAAAAAAAAAAjgIAAAAAAACOAgAAAAAAAAcAAABjb21tZW50jgIAAAAAAACPAgAAAAAAAAAAAACPAgAAAAAAAJACAAAAAAAAAAAAAJACAAAAAAAAkQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQIAAAAAAACUAgAAAAAAAAAAAAAAAPC/" - ], - [ - 324, - 1, - "right_delete", - null, - "AQAAAJECAAAAAAAAkQIAAAAAAAADAAAALmlk", - "AQAAAAAAAAABAAAAkQIAAAAAAACUAgAAAAAAAAAAAAAAAPC/" - ], - [ - 326, - 1, - "insert", - { - "characters": ".se" - }, - "AwAAAJICAAAAAAAAkwIAAAAAAAAAAAAAkwIAAAAAAACUAgAAAAAAAAAAAACUAgAAAAAAAJUCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgIAAAAAAACSAgAAAAAAAAAAAAAAAPC/" - ], - [ - 327, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.select\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":27,\"character\":49},\"symbolLabel\":\"select\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"select\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "select" - }, - "AgAAAJMCAAAAAAAAkwIAAAAAAAACAAAAc2WTAgAAAAAAAJkCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlQIAAAAAAACVAgAAAAAAAAAAAAAAAPC/" - ], - [ - 328, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAJkCAAAAAAAAmwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAmQIAAAAAAACZAgAAAAAAAAAAAAAAAPC/" - ], - [ - 333, - 1, - "right_delete", - null, - "AQAAAKACAAAAAAAAoAIAAAAAAAA5AAAAcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5ub3RpZmllZC5pc19udWxsKCkp", - "AQAAAAAAAAABAAAAoAIAAAAAAADZAgAAAAAAAP///////+9/" - ], - [ - 335, - 1, - "left_delete", - null, - "AQAAAJsCAAAAAAAAmwIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAnAIAAAAAAACcAgAAAAAAAAAAAAAAAAAA" - ], - [ - 337, - 1, - "trim_trailing_white_space", - null, - "AQAAAJsCAAAAAAAAmwIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAmwIAAAAAAACbAgAAAAAAAAAAAAAAAPC/" - ], - [ - 344, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAMACAAAAAAAAwQIAAAAAAAAAAAAAwQIAAAAAAADFAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwAIAAAAAAADAAgAAAAAAAP///////+9/" - ], - [ - 345, - 1, - "paste", - null, - "AQAAAMUCAAAAAAAA+AIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQIAAAAAAADFAgAAAAAAAAAAAAAAAPC/" - ], - [ - 351, - 1, - "insert", - { - "characters": "published" - }, - "CgAAAN4CAAAAAAAA3wIAAAAAAAAAAAAA3wIAAAAAAADfAgAAAAAAAAgAAABub3RpZmllZN8CAAAAAAAA4AIAAAAAAAAAAAAA4AIAAAAAAADhAgAAAAAAAAAAAADhAgAAAAAAAOICAAAAAAAAAAAAAOICAAAAAAAA4wIAAAAAAAAAAAAA4wIAAAAAAADkAgAAAAAAAAAAAADkAgAAAAAAAOUCAAAAAAAAAAAAAOUCAAAAAAAA5gIAAAAAAAAAAAAA5gIAAAAAAADnAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3gIAAAAAAADmAgAAAAAAAAAAAAAAAPC/" - ], - [ - 355, - 1, - "right_delete", - null, - "AQAAAP4CAAAAAAAA/gIAAAAAAAA6AAAAcmV0dXJuIENvbW1lbnQuc2VsZWN0KCkud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbCgpKQ", - "AQAAAAAAAAABAAAA/gIAAAAAAAA4AwAAAAAAAP///////+9/" - ], - [ - 357, - 1, - "left_delete", - null, - "AQAAAPkCAAAAAAAA+QIAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAA+gIAAAAAAAD6AgAAAAAAAAAAAAAAAAAA" - ], - [ - 360, - 1, - "trim_trailing_white_space", - null, - "AQAAAPkCAAAAAAAA+QIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAA/gIAAAAAAAD+AgAAAAAAAAAAAAAAgH9A" - ], - [ - 363, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAACQDAAAAAAAAJQMAAAAAAAAAAAAAJQMAAAAAAAApAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJAMAAAAAAAAkAwAAAAAAAP///////+9/" - ], - [ - 364, - 1, - "insert", - { - "characters": "return" - }, - "BgAAACkDAAAAAAAAKgMAAAAAAAAAAAAAKgMAAAAAAAArAwAAAAAAAAAAAAArAwAAAAAAACwDAAAAAAAAAAAAACwDAAAAAAAALQMAAAAAAAAAAAAALQMAAAAAAAAuAwAAAAAAAAAAAAAuAwAAAAAAAC8DAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKQMAAAAAAAApAwAAAAAAAAAAAAAAAPC/" - ], - [ - 365, - 1, - "insert", - { - "characters": " " - }, - "AQAAAC8DAAAAAAAAMAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALwMAAAAAAAAvAwAAAAAAAAAAAAAAAPC/" - ], - [ - 372, - 1, - "paste", - null, - "AQAAADADAAAAAAAAXQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMAMAAAAAAAAwAwAAAAAAAP///////+9/" - ], - [ - 377, - 1, - "insert", - { - "characters": "url" - }, - "BAAAAEIDAAAAAAAAQwMAAAAAAAAAAAAAQwMAAAAAAABDAwAAAAAAAAkAAABwdWJsaXNoZWRDAwAAAAAAAEQDAAAAAAAAAAAAAEQDAAAAAAAARQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQgMAAAAAAABLAwAAAAAAAAAAAAAAAPC/" - ], - [ - 380, - 1, - "insert", - { - "characters": "url" - }, - "BAAAAEkDAAAAAAAASgMAAAAAAAAAAAAASgMAAAAAAABKAwAAAAAAAAQAAABOb25lSgMAAAAAAABLAwAAAAAAAAAAAABLAwAAAAAAAEwDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASQMAAAAAAABNAwAAAAAAAAAAAAAAAPC/" - ], - [ - 381, - 1, - "insert", - { - "characters": " &&" - }, - "AwAAAEwDAAAAAAAATQMAAAAAAAAAAAAATQMAAAAAAABOAwAAAAAAAAAAAABOAwAAAAAAAE8DAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATAMAAAAAAABMAwAAAAAAAAAAAAAAAPC/" - ], - [ - 382, - 1, - "insert", - { - "characters": " " - }, - "AQAAAE8DAAAAAAAAUAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" - ], - [ - 383, - 1, - "left_delete", - null, - "AQAAAE8DAAAAAAAATwMAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAUAMAAAAAAABQAwAAAAAAAAAAAAAAAPC/" - ], - [ - 384, - 1, - "insert", - { - "characters": " " - }, - "AQAAAE8DAAAAAAAAUAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" - ], - [ - 385, - 2, - "left_delete", - null, - "AgAAAE8DAAAAAAAATwMAAAAAAAABAAAAIE4DAAAAAAAATgMAAAAAAAABAAAAJg", - "AQAAAAAAAAABAAAAUAMAAAAAAABQAwAAAAAAAAAAAAAAAPC/" - ], - [ - 386, - 1, - "insert", - { - "characters": " " - }, - "AQAAAE4DAAAAAAAATwMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgMAAAAAAABOAwAAAAAAAAAAAAAAAPC/" - ], - [ - 394, - 1, - "paste", - null, - "AQAAAE8DAAAAAAAAXAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwMAAAAAAABPAwAAAAAAAAAAAAAAAPC/" - ], - [ - 395, - 1, - "insert", - { - "characters": "published" - }, - "CQAAAFwDAAAAAAAAXQMAAAAAAAAAAAAAXQMAAAAAAABeAwAAAAAAAAAAAABeAwAAAAAAAF8DAAAAAAAAAAAAAF8DAAAAAAAAYAMAAAAAAAAAAAAAYAMAAAAAAABhAwAAAAAAAAAAAABhAwAAAAAAAGIDAAAAAAAAAAAAAGIDAAAAAAAAYwMAAAAAAAAAAAAAYwMAAAAAAABkAwAAAAAAAAAAAABkAwAAAAAAAGUDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXAMAAAAAAABcAwAAAAAAAAAAAAAAAPC/" - ], - [ - 396, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAGUDAAAAAAAAZgMAAAAAAAAAAAAAZgMAAAAAAABnAwAAAAAAAAAAAABnAwAAAAAAAGgDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZQMAAAAAAABlAwAAAAAAAAAAAAAAAPC/" - ], - [ - 397, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAGgDAAAAAAAAaQMAAAAAAAAAAAAAaQMAAAAAAABqAwAAAAAAAAAAAABqAwAAAAAAAGsDAAAAAAAAAAAAAGsDAAAAAAAAbAMAAAAAAAAAAAAAbAMAAAAAAABtAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaAMAAAAAAABoAwAAAAAAAAAAAAAAAPC/" - ], - [ - 401, - 1, - "insert", - { - "characters": "!" - }, - "AgAAAGYDAAAAAAAAZwMAAAAAAAAAAAAAZwMAAAAAAABnAwAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAZgMAAAAAAABnAwAAAAAAAAAAAAAAAPC/" - ], - [ - 406, - 1, - "insert", - { - "characters": "(" - }, - "AQAAADUDAAAAAAAANgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQMAAAAAAAA1AwAAAAAAAAAAAAAAAPC/" - ], - [ - 409, - 1, - "insert", - { - "characters": ")" - }, - "AQAAAE0DAAAAAAAATgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQMAAAAAAABNAwAAAAAAAAAAAAAAAPC/" - ], - [ - 412, - 1, - "insert", - { - "characters": "(" - }, - "AQAAAFEDAAAAAAAAUgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQMAAAAAAABRAwAAAAAAAAAAAAAAAPC/" - ], - [ - 416, - 1, - "insert", - { - "characters": ")" - }, - "AQAAAHEDAAAAAAAAcgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcQMAAAAAAABxAwAAAAAAAAAAAAAAAPC/" - ], - [ - 420, - 1, - "insert", - { - "characters": ".sot" - }, - "BAAAAHsDAAAAAAAAfAMAAAAAAAAAAAAAfAMAAAAAAAB9AwAAAAAAAAAAAAB9AwAAAAAAAH4DAAAAAAAAAAAAAH4DAAAAAAAAfwMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAewMAAAAAAAB7AwAAAAAAAAAAAAAAAPC/" - ], - [ - 421, - 1, - "left_delete", - null, - "AQAAAH4DAAAAAAAAfgMAAAAAAAABAAAAdA", - "AQAAAAAAAAABAAAAfwMAAAAAAAB/AwAAAAAAAAAAAAAAAPC/" - ], - [ - 422, - 1, - "insert", - { - "characters": "rt-" - }, - "AwAAAH4DAAAAAAAAfwMAAAAAAAAAAAAAfwMAAAAAAACAAwAAAAAAAAAAAACAAwAAAAAAAIEDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgMAAAAAAAB+AwAAAAAAAAAAAAAAAPC/" - ], - [ - 423, - 1, - "left_delete", - null, - "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", - "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" - ], - [ - 424, - 1, - "insert", - { - "characters": "-" - }, - "AQAAAIADAAAAAAAAgQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" - ], - [ - 425, - 1, - "left_delete", - null, - "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", - "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" - ], - [ - 426, - 1, - "insert", - { - "characters": "-" - }, - "AQAAAIADAAAAAAAAgQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" - ], - [ - 427, - 1, - "left_delete", - null, - "AQAAAIADAAAAAAAAgAMAAAAAAAABAAAALQ", - "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" - ], - [ - 428, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAIADAAAAAAAAggMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAMAAAAAAACAAwAAAAAAAAAAAAAAAPC/" - ], - [ - 429, - 1, - "insert", - { - "characters": "lamba" - }, - "BQAAAIEDAAAAAAAAggMAAAAAAAAAAAAAggMAAAAAAACDAwAAAAAAAAAAAACDAwAAAAAAAIQDAAAAAAAAAAAAAIQDAAAAAAAAhQMAAAAAAAAAAAAAhQMAAAAAAACGAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" - ], - [ - 430, - 1, - "left_delete", - null, - "AQAAAIUDAAAAAAAAhQMAAAAAAAABAAAAYQ", - "AQAAAAAAAAABAAAAhgMAAAAAAACGAwAAAAAAAAAAAAAAAPC/" - ], - [ - 431, - 1, - "insert", - { - "characters": "da:" - }, - "AwAAAIUDAAAAAAAAhgMAAAAAAAAAAAAAhgMAAAAAAACHAwAAAAAAAAAAAACHAwAAAAAAAIgDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQMAAAAAAACFAwAAAAAAAAAAAAAAAPC/" - ], - [ - 432, - 1, - "insert", - { - "characters": " sort" - }, - "BQAAAIgDAAAAAAAAiQMAAAAAAAAAAAAAiQMAAAAAAACKAwAAAAAAAAAAAACKAwAAAAAAAIsDAAAAAAAAAAAAAIsDAAAAAAAAjAMAAAAAAAAAAAAAjAMAAAAAAACNAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiAMAAAAAAACIAwAAAAAAAAAAAAAAAPC/" - ], - [ - 433, - 1, - "insert", - { - "characters": " " - }, - "AQAAAI0DAAAAAAAAjgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" - ], - [ - 437, - 1, - "insert", - { - "characters": "row:" - }, - "BQAAAIkDAAAAAAAAigMAAAAAAAAAAAAAigMAAAAAAACKAwAAAAAAAAQAAABzb3J0igMAAAAAAACLAwAAAAAAAAAAAACLAwAAAAAAAIwDAAAAAAAAAAAAAIwDAAAAAAAAjQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" - ], - [ - 438, - 1, - "insert", - { - "characters": " " - }, - "AQAAAI0DAAAAAAAAjgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQMAAAAAAACNAwAAAAAAAAAAAAAAAPC/" - ], - [ - 439, - 1, - "insert", - { - "characters": "row" - }, - "AwAAAI4DAAAAAAAAjwMAAAAAAAAAAAAAjwMAAAAAAACQAwAAAAAAAAAAAACQAwAAAAAAAJEDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjgMAAAAAAACOAwAAAAAAAAAAAAAAAPC/" - ], - [ - 440, - 1, - "insert", - { - "characters": ".publ" - }, - "BQAAAJEDAAAAAAAAkgMAAAAAAAAAAAAAkgMAAAAAAACTAwAAAAAAAAAAAACTAwAAAAAAAJQDAAAAAAAAAAAAAJQDAAAAAAAAlQMAAAAAAAAAAAAAlQMAAAAAAACWAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkQMAAAAAAACRAwAAAAAAAAAAAAAAAPC/" - ], - [ - 441, - 1, - "insert", - { - "characters": "ish" - }, - "AwAAAJYDAAAAAAAAlwMAAAAAAAAAAAAAlwMAAAAAAACYAwAAAAAAAAAAAACYAwAAAAAAAJkDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlgMAAAAAAACWAwAAAAAAAAAAAAAAAPC/" - ], - [ - 442, - 1, - "insert", - { - "characters": "ed" - }, - "AgAAAJkDAAAAAAAAmgMAAAAAAAAAAAAAmgMAAAAAAACbAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAmQMAAAAAAACZAwAAAAAAAAAAAAAAAPC/" - ], - [ - 443, - 1, - "right_delete", - null, - "AQAAAJsDAAAAAAAAmwMAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAmwMAAAAAAACbAwAAAAAAAAAAAAAAAPC/" - ], - [ - 448, - 1, - "right_delete", - null, - "AQAAAJ0DAAAAAAAAnQMAAAAAAACjAAAAICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5vcmRlcl9ieSgrQ29tbWVudC5wdWJsaXNoZWQpCiAgICApCg", - "AQAAAAAAAAABAAAAnQMAAAAAAABABAAAAAAAAAAAAAAAAAAA" - ], - [ - 451, - 1, - "left_delete", - null, - "AQAAAIcDAAAAAAAAhwMAAAAAAAABAAAAOg", - "AQAAAAAAAAABAAAAiAMAAAAAAACIAwAAAAAAAAAAAAAAAPC/" - ], - [ - 461, - 1, - "right_delete", - null, - "AQAAAB4FAAAAAAAAHgUAAAAAAAAeAAAALnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIp", - "AQAAAAAAAAABAAAAHgUAAAAAAAA8BQAAAAAAAP///////+9/" - ], - [ - 470, - 1, - "insert", - { - "characters": "\n\n" - }, - "BQAAAMADAAAAAAAAwQMAAAAAAAAAAAAAwQMAAAAAAADFAwAAAAAAAAAAAADFAwAAAAAAAMYDAAAAAAAAAAAAAMYDAAAAAAAAygMAAAAAAAAAAAAAwQMAAAAAAADBAwAAAAAAAAQAAAAgICAg", - "AQAAAAAAAAABAAAAwAMAAAAAAADAAwAAAAAAAAAAAAAAAPC/" - ], - [ - 472, - 1, - "reindent", - null, - "AQAAAMEDAAAAAAAAxQMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQMAAAAAAADBAwAAAAAAAAAAAAAAAEJA" - ], - [ - 473, - 1, - "insert", - { - "characters": "return" - }, - "BwAAAMUDAAAAAAAAxgMAAAAAAAAAAAAAxwMAAAAAAADHAwAAAAAAAAQAAAAgICAgxgMAAAAAAADHAwAAAAAAAAAAAADHAwAAAAAAAMgDAAAAAAAAAAAAAMgDAAAAAAAAyQMAAAAAAAAAAAAAyQMAAAAAAADKAwAAAAAAAAAAAADKAwAAAAAAAMsDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQMAAAAAAADFAwAAAAAAAAAAAAAAAPC/" - ], - [ - 474, - 1, - "insert", - { - "characters": " " - }, - "AQAAAMsDAAAAAAAAzAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAywMAAAAAAADLAwAAAAAAAAAAAAAAAPC/" - ], - [ - 480, - 1, - "paste", - null, - "AQAAAMwDAAAAAAAAFwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzAMAAAAAAADMAwAAAAAAAAAAAAAAAPC/" - ], - [ - 485, - 1, - "insert", - { - "characters": "==" - }, - "AwAAAAUEAAAAAAAABgQAAAAAAAAAAAAABgQAAAAAAAAGBAAAAAAAAAIAAAAhPQYEAAAAAAAABwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABQQAAAAAAAAHBAAAAAAAAAAAAAAAAPC/" - ], - [ - 487, - 1, - "insert", - { - "characters": " " - }, - "AQAAABcEAAAAAAAAGAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFwQAAAAAAAAXBAAAAAAAAP///////+9/" - ], - [ - 488, - 1, - "left_delete", - null, - "AQAAABcEAAAAAAAAFwQAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAGAQAAAAAAAAYBAAAAAAAAAAAAAAAAPC/" - ], - [ - 489, - 1, - "insert", - { - "characters": ".count" - }, - "BgAAABcEAAAAAAAAGAQAAAAAAAAAAAAAGAQAAAAAAAAZBAAAAAAAAAAAAAAZBAAAAAAAABoEAAAAAAAAAAAAABoEAAAAAAAAGwQAAAAAAAAAAAAAGwQAAAAAAAAcBAAAAAAAAAAAAAAcBAAAAAAAAB0EAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFwQAAAAAAAAXBAAAAAAAAAAAAAAAAPC/" - ], - [ - 490, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAB0EAAAAAAAAHwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHQQAAAAAAAAdBAAAAAAAAAAAAAAAAPC/" - ], - [ - 492, - 1, - "insert", - { - "characters": " if" - }, - "AwAAAB8EAAAAAAAAIAQAAAAAAAAAAAAAIAQAAAAAAAAhBAAAAAAAAAAAAAAhBAAAAAAAACIEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHwQAAAAAAAAfBAAAAAAAAAAAAAAAAPC/" - ], - [ - 493, - 1, - "insert", - { - "characters": " u" - }, - "AgAAACIEAAAAAAAAIwQAAAAAAAAAAAAAIwQAAAAAAAAkBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIgQAAAAAAAAiBAAAAAAAAAAAAAAAAPC/" - ], - [ - 494, - 1, - "insert", - { - "characters": "rl" - }, - "AgAAACQEAAAAAAAAJQQAAAAAAAAAAAAAJQQAAAAAAAAmBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJAQAAAAAAAAkBAAAAAAAAAAAAAAAAPC/" - ], - [ - 495, - 1, - "insert", - { - "characters": " " - }, - "AQAAACYEAAAAAAAAJwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 496, - 1, - "insert", - { - "characters": "\nelse" - }, - "BgAAACcEAAAAAAAAKAQAAAAAAAAAAAAAKAQAAAAAAAAsBAAAAAAAAAAAAAAsBAAAAAAAAC0EAAAAAAAAAAAAAC0EAAAAAAAALgQAAAAAAAAAAAAALgQAAAAAAAAvBAAAAAAAAAAAAAAvBAAAAAAAADAEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJwQAAAAAAAAnBAAAAAAAAAAAAAAAAPC/" - ], - [ - 497, - 1, - "insert", - { - "characters": " " - }, - "AQAAADAEAAAAAAAAMQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMAQAAAAAAAAwBAAAAAAAAAAAAAAAAPC/" - ], - [ - 504, - 1, - "paste", - null, - "AQAAADEEAAAAAAAAjAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMQQAAAAAAAAxBAAAAAAAAAAAAAAAAPC/" - ], - [ - 511, - 1, - "right_delete", - null, - "AQAAADYEAAAAAAAANgQAAAAAAAAdAAAAKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmICg", - "AQAAAAAAAAABAAAANgQAAAAAAABTBAAAAAAAAAAAAAAAAPC/" - ], - [ - 515, - 1, - "left_delete", - null, - "AQAAAFQEAAAAAAAAVAQAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAAVQQAAAAAAABVBAAAAAAAAAAAAAAAAPC/" - ], - [ - 520, - 1, - "right_delete", - null, - "AQAAAGcEAAAAAAAAZwQAAAAAAAAGAAAAaWYgdXJs", - "AQAAAAAAAAABAAAAZwQAAAAAAABtBAAAAAAAAAAAAAAAAPC/" - ], - [ - 527, - 1, - "insert", - { - "characters": "is" - }, - "AwAAAAUEAAAAAAAABgQAAAAAAAAAAAAABgQAAAAAAAAGBAAAAAAAAAIAAAA9PQYEAAAAAAAABwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABQQAAAAAAAAHBAAAAAAAAAAAAAAAAPC/" - ], - [ - 532, - 1, - "insert", - { - "characters": "is" - }, - "AwAAAE0EAAAAAAAATgQAAAAAAAAAAAAATgQAAAAAAABOBAAAAAAAAAIAAAA9PU4EAAAAAAAATwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQQAAAAAAABPBAAAAAAAAAAAAAAAAPC/" - ], - [ - 537, - 1, - "right_delete", - null, - "AQAAAGoEAAAAAAAAagQAAAAAAADyAAAAICAgIHJldHVybiAoCiAgICAgICAgQ29tbWVudC5zZWxlY3QoQ29tbWVudCkKICAgICAgICAud2hlcmUoKENvbW1lbnQudXJsID09IHVybCkgJiAoQ29tbWVudC5wdWJsaXNoZWQuaXNfbnVsbChGYWxzZSkpKQogICAgICAgIC5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBDb21tZW50LnNlbGVjdChDb21tZW50KS53aGVyZShDb21tZW50LnB1Ymxpc2hlZC5pc19udWxsKEZhbHNlKSkuY291bnQoKQogICAgKQo", - "AQAAAAAAAAABAAAAagQAAAAAAABcBQAAAAAAAAAAAAAAAAAA" - ], - [ - 538, - 1, - "left_delete", - null, - "AQAAAGkEAAAAAAAAaQQAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAagQAAAAAAABqBAAAAAAAAAAAAAAAAPC/" - ], - [ - 540, - 1, - "trim_trailing_white_space", - null, - "AgAAAGYEAAAAAAAAZgQAAAAAAAACAAAAICAmBAAAAAAAACYEAAAAAAAAAQAAACA", - "AQAAAAAAAAABAAAAaQQAAAAAAABpBAAAAAAAAAAAAAAAAPC/" - ], - [ - 546, - 1, - "insert", - { - "characters": "\t" - }, - "AQAAACsEAAAAAAAALwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKwQAAAAAAAArBAAAAAAAAAAAAAAAAPC/" - ], - [ - 554, - 1, - "right_delete", - null, - "AQAAACYEAAAAAAAAJgQAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 555, - 1, - "insert", - { - "characters": " " - }, - "AQAAACYEAAAAAAAAJwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 558, - 1, - "right_delete", - null, - "AQAAACcEAAAAAAAAJwQAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAJwQAAAAAAAAvBAAAAAAAAAAAAAAAAPC/" - ], - [ - 566, - 1, "insert", { - "characters": "is" + "characters": "\nret" }, - "AwAAAOgCAAAAAAAA6QIAAAAAAAAAAAAA6QIAAAAAAADpAgAAAAAAAAIAAAA9PekCAAAAAAAA6gIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6AIAAAAAAADqAgAAAAAAAAAAAAAAAPC/" + "BQAAAPAAAAAAAAAA8QAAAAAAAAAAAAAA8QAAAAAAAAD1AAAAAAAAAAAAAAD1AAAAAAAAAPYAAAAAAAAAAAAAAPYAAAAAAAAA9wAAAAAAAAAAAAAA9wAAAAAAAAD4AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAPC/" ], [ - 571, + 231, 1, "insert", { - "characters": "is" + "characters": "urn" }, - "AwAAAIoCAAAAAAAAiwIAAAAAAAAAAAAAiwIAAAAAAACLAgAAAAAAAAIAAAA9PYsCAAAAAAAAjAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjAIAAAAAAACKAgAAAAAAAAAAAAAAAPC/" + "AwAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" ], [ - 578, + 232, 1, "insert", { - "characters": "is" + "characters": " comment" }, - "AgAAAGkDAAAAAAAAagMAAAAAAAAAAAAAagMAAAAAAABrAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaQMAAAAAAABpAwAAAAAAAAAAAAAAAPC/" - ], - [ - 579, - 1, - "insert", - { - "characters": " not" - }, - "BAAAAGsDAAAAAAAAbAMAAAAAAAAAAAAAbAMAAAAAAABtAwAAAAAAAAAAAABtAwAAAAAAAG4DAAAAAAAAAAAAAG4DAAAAAAAAbwMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAawMAAAAAAABrAwAAAAAAAAAAAAAAAPC/" - ], - [ - 580, - 1, - "insert", - { - "characters": " " - }, - "AQAAAG8DAAAAAAAAcAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbwMAAAAAAABvAwAAAAAAAAAAAAAAAPC/" - ], - [ - 582, - 2, - "right_delete", - null, - "AgAAAHADAAAAAAAAcAMAAAAAAAACAAAAIT1wAwAAAAAAAHADAAAAAAAAAQAAACA", - "AQAAAAAAAAABAAAAcAMAAAAAAAByAwAAAAAAAAAAAAAAAPC/" - ], - [ - 589, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAANEEAAAAAAAA0gQAAAAAAAAAAAAA0gQAAAAAAADWBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0QQAAAAAAADRBAAAAAAAAAAAAAAAAPC/" - ], - [ - 595, - 1, - "paste", - null, - "AQAAANYEAAAAAAAA+gQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1gQAAAAAAADWBAAAAAAAAAAAAAAAAPC/" - ], - [ - 600, - 1, - "right_delete", - null, - "AQAAAOoEAAAAAAAA6gQAAAAAAAAPAAAAYXNkaWN0KGNvbW1lbnQp", - "AQAAAAAAAAABAAAA6gQAAAAAAAD5BAAAAAAAAAAAAAAAAPC/" - ], - [ - 601, - 1, - "insert", - { - "characters": "\n" - }, - "BAAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA6wQAAAAAAADvBAAAAAAAAAAAAADrBAAAAAAAAOsEAAAAAAAABAAAACAgICDrBAAAAAAAAPMEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAAAAAAAAAPC/" - ], - [ - 604, - 1, - "insert", - { - "characters": "\n" - }, - "BAAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA6wQAAAAAAADvBAAAAAAAAAAAAADrBAAAAAAAAOsEAAAAAAAABAAAACAgICDrBAAAAAAAAPMEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAAAAAAAAAPC/" - ], - [ - 607, - 1, - "cut", - null, - "AQAAABUFAAAAAAAAFQUAAAAAAADcAAAAICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9Y3JlYXRlZCwKICAgICAgICBub3RpZmllZD1Ob25lLAogICAgICAgIHB1Ymxpc2hlZD1Ob25lLA", - "AQAAAAAAAAABAAAA8QUAAAAAAAAVBQAAAAAAAAAAAAAAAPC/" - ], - [ - 610, - 1, - "paste", - null, - "AQAAAOwEAAAAAAAAyAUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7AQAAAAAAADsBAAAAAAAAAAAAAAAAPC/" - ], - [ - 613, - 1, - "right_delete", - null, - "AQAAAPMEAAAAAAAA8wQAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAA8wQAAAAAAADzBAAAAAAAAAAAAAAAAPC/" - ], - [ - 618, - 1, - "cut", - null, - "AQAAAMMEAAAAAAAAwwQAAAAAAAAOAAAAZGF0ZXRpbWUubm93KCk", - "AQAAAAAAAAABAAAAwwQAAAAAAADRBAAAAAAAAAAAAAAAAPC/" - ], - [ - 623, - 1, - "paste", - null, - "AgAAAIIFAAAAAAAAkAUAAAAAAAAAAAAAkAUAAAAAAACQBQAAAAAAAAcAAABjcmVhdGVk", - "AQAAAAAAAAABAAAAggUAAAAAAACJBQAAAAAAAAAAAAAAAPC/" - ], - [ - 626, - 2, - "left_delete", - null, - "AgAAANIFAAAAAAAA0gUAAAAAAAAwAAAAICAgIGNvbW1lbnQgPSBDb21tZW50KAoKICAgICkKICAgIGNvbW1lbnQuc2F2ZSgp0QUAAAAAAADRBQAAAAAAAAEAAAAK", - "AQAAAAAAAAABAAAAAgYAAAAAAADSBQAAAAAAAAAAAAAAAPC/" - ], - [ - 631, - 1, - "right_delete", - null, - "AQAAALkEAAAAAAAAuQQAAAAAAAAKAAAAY3JlYXRlZCA9IA", - "AQAAAAAAAAABAAAAwwQAAAAAAAC5BAAAAAAAAAAAAAAAAEJA" - ], - [ - 633, - 1, - "left_delete", - null, - "AQAAALQEAAAAAAAAtAQAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAtQQAAAAAAAC1BAAAAAAAAAAAAAAAAAAA" - ], - [ - 635, - 1, - "trim_trailing_white_space", - null, - "AgAAALUFAAAAAAAAtQUAAAAAAAAHAAAAICAgICAgILQEAAAAAAAAtAQAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAtAQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" + "CAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA/AAAAAAAAAD9AAAAAAAAAAAAAAD9AAAAAAAAAP4AAAAAAAAAAAAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA/wAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAEBAAAAAAAAAAAAAAEBAAAAAAAAAgEAAAAAAAAAAAAAAgEAAAAAAAADAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+wAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" ], [ - 651, + 233, 1, "insert", { - "characters": "comment" + "characters": ".id" }, - "BwAAALkEAAAAAAAAugQAAAAAAAAAAAAAugQAAAAAAAC7BAAAAAAAAAAAAAC7BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAL8EAAAAAAAAwAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuQQAAAAAAAC5BAAAAAAAAAAAAAAAAPC/" + "AwAAAAMBAAAAAAAABAEAAAAAAAAAAAAABAEAAAAAAAAFAQAAAAAAAAAAAAAFAQAAAAAAAAYBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAwEAAAAAAAADAQAAAAAAAAAAAAAAAPC/" ], [ - 652, + 234, 1, "insert", { "characters": " =" }, - "AgAAAMAEAAAAAAAAwQQAAAAAAAAAAAAAwQQAAAAAAADCBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwAQAAAAAAADABAAAAAAAAAAAAAAAAPC/" + "AgAAAAYBAAAAAAAABwEAAAAAAAAAAAAABwEAAAAAAAAIAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABgEAAAAAAAAGAQAAAAAAAAAAAAAAAPC/" ], [ - 653, + 235, 1, "insert", { "characters": " " }, - "AQAAAMIEAAAAAAAAwwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwgQAAAAAAADCBAAAAAAAAAAAAAAAAPC/" + "AQAAAAgBAAAAAAAACQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACAEAAAAAAAAIAQAAAAAAAAAAAAAAAPC/" ], [ - 660, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADZBQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKCmRlZiBmaW5kX2NvbW1lbnRfYnlfaWQoY29tbWVudF9pZCk6CiAgICByZXR1cm4gZGIoKS5jb21tZW50KGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5ub3RpZmllZCA9IGRhdGV0aW1lLm5vdygpCiAgICBkYigpLmNvbW1lbnQuaW5zZXJ0KGFzZGljdChjb21tZW50KSkKCgpkZWYgcHVibGlzaF9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgY29tbWVudC5wdWJsaXNoZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIGRlbGV0ZV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgZGIoKShkYigpLmNvbW1lbnQuaWQgPT0gY29tbWVudC5pZCkuZGVsZXRlKCkKCgpkZWYgZmluZF9ub3Rfbm90aWZpZWRfY29tbWVudHMoKToKICAgIHJldHVybiBkYigpKGRiKCkuY29tbWVudC5ub3RpZmllZCBpcyBOb25lKS5zZWxlY3QoKQoKCmRlZiBmaW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKToKICAgIHJldHVybiBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgaXMgTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9wdWJsaXNoZWRfY29tbWVudHNfYnlfdXJsKHVybCk6CiAgICByZXR1cm4gZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgaXMgbm90IE5vbmUpKS5zZWxlY3QoKS5zb3J0KGxhbWJkYSByb3c6IHJvdy5wdWJsaXNoZWQpCgoKZGVmIGNvdW50X3B1Ymxpc2hlZF9jb21tZW50cyh1cmwpOgogICAgcmV0dXJuIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkIGlzIE5vbmUpKS5zZWxlY3QoKS5jb3VudCgpIGlmIHVybCBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCBpcyBOb25lKS5zZWxlY3QoKS5jb3VudCgpCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY29tbWVudCA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICAgICAgKQogICAgcmV0dXJuIGNvbW1lbnQKAAAAAAAAAAAqBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQUAAAAAAADFBQAAAAAAAAAAAAAAAPC/" - ], - [ - 691, - 1, - "insert", - { - "characters": "==" - }, - "AgAAAIsCAAAAAAAAjAIAAAAAAAAAAAAAjAIAAAAAAACNAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwIAAAAAAACLAgAAAAAAAAAAAAAAAPC/" - ], - [ - 692, - 2, - "right_delete", - null, - "AgAAAI0CAAAAAAAAjQIAAAAAAAABAAAAaY0CAAAAAAAAjQIAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAAjQIAAAAAAACNAgAAAAAAAAAAAAAAAPC/" - ], - [ - 696, - 1, - "insert", - { - "characters": "==" - }, - "AgAAAOkCAAAAAAAA6gIAAAAAAAAAAAAA6gIAAAAAAADrAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6QIAAAAAAADpAgAAAAAAAAAAAAAAAPC/" - ], - [ - 697, - 2, - "right_delete", - null, - "AgAAAOsCAAAAAAAA6wIAAAAAAAABAAAAaesCAAAAAAAA6wIAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAA6wIAAAAAAADrAgAAAAAAAAAAAAAAAPC/" - ], - [ - 712, - 1, - "insert", - { - "characters": "!=" - }, - "AwAAAHQDAAAAAAAAdQMAAAAAAAAAAAAAdQMAAAAAAAB1AwAAAAAAAAYAAABpcyBub3R1AwAAAAAAAHYDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdAMAAAAAAAB6AwAAAAAAAAAAAAAAAPC/" - ], - [ - 717, - 1, - "insert", - { - "characters": "==" - }, - "AwAAADIEAAAAAAAAMwQAAAAAAAAAAAAAMwQAAAAAAAAzBAAAAAAAAAIAAABpczMEAAAAAAAANAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMgQAAAAAAAA0BAAAAAAAAAAAAAAAAPC/" - ], - [ - 725, - 1, - "insert", - { - "characters": "==" - }, - "AwAAAJcEAAAAAAAAmAQAAAAAAAAAAAAAmAQAAAAAAACYBAAAAAAAAAIAAABpc5gEAAAAAAAAmQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlwQAAAAAAACZBAAAAAAAAAAAAAAAAPC/" - ], - [ - 741, - 2, - "right_delete", - null, - "AgAAAKAEAAAAAAAAoAQAAAAAAAAIAAAAc2VsZWN0KCmgBAAAAAAAAKAEAAAAAAAAAQAAAC4", - "AQAAAAAAAAABAAAAoAQAAAAAAACoBAAAAAAAAAAAAAAAAPC/" - ], - [ - 744, - 1, - "right_delete", - null, - "AQAAAEQEAAAAAAAARAQAAAAAAAAJAAAALnNlbGVjdCgp", - "AQAAAAAAAAABAAAATQQAAAAAAABEBAAAAAAAAAAAAAAAAFJA" - ], - [ - 746, + 236, 1, "left_delete", null, - "AQAAADsEAAAAAAAAOwQAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAPAQAAAAAAAA8BAAAAAAAAAAAAAAAAAAA" + "AQAAAAgBAAAAAAAACAEAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" ], [ - 748, - 1, - "trim_trailing_white_space", - null, - "AQAAADsEAAAAAAAAOwQAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAOwQAAAAAAAA7BAAAAAAAAAAAAAAAAPC/" - ], - [ - 749, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAAALBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIHB1Ymxpc2hfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5pbnNlcnQoYXNkaWN0KGNvbW1lbnQpKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KCkKICAgICAgICAuc29ydChsYW1iZGEgcm93OiByb3cucHVibGlzaGVkKQogICAgKQoKCmRlZiBjb3VudF9wdWJsaXNoZWRfY29tbWVudHModXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkpCiAgICAgICAgLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgY29tbWVudCA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICByZXR1cm4gY29tbWVudAoAAAAAAAAAAAIGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOwQAAAAAAAA7BAAAAAAAAAAAAAAAAPC/" - ], - [ - 790, + 237, 1, "insert", { - "characters": "\n" + "characters": "=" }, - "AgAAAO4FAAAAAAAA7wUAAAAAAAAAAAAA7wUAAAAAAADzBQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7gUAAAAAAADuBQAAAAAAAAAAAAAAAPC/" + "AQAAAAgBAAAAAAAACQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACAEAAAAAAAAIAQAAAAAAAAAAAAAAAPC/" ], [ - 796, + 238, 1, "insert", { - "characters": "Comment" + "characters": " other.id" }, - "CQAAAP8FAAAAAAAAAAYAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAcAAABjb21tZW507wUAAAAAAADvBQAAAAAAAAQAAAAgICAg/AUAAAAAAAD9BQAAAAAAAAAAAAD9BQAAAAAAAP4FAAAAAAAAAAAAAP4FAAAAAAAA/wUAAAAAAAAAAAAA/wUAAAAAAAAABgAAAAAAAAAAAAAABgAAAAAAAAEGAAAAAAAAAAAAAAEGAAAAAAAAAgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wUAAAAAAAAGBgAAAAAAAAAAAAAAAPC/" + "CQAAAAkBAAAAAAAACgEAAAAAAAAAAAAACgEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAARAQAAAAAAABIBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" ], [ - 797, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAAIGAAAAAAAABAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAgYAAAAAAAACBgAAAAAAAAAAAAAAAPC/" - ], - [ - 798, - 1, - "insert", - { - "characters": "id=com" - }, - "BgAAAAMGAAAAAAAABAYAAAAAAAAAAAAABAYAAAAAAAAFBgAAAAAAAAAAAAAFBgAAAAAAAAYGAAAAAAAAAAAAAAYGAAAAAAAABwYAAAAAAAAAAAAABwYAAAAAAAAIBgAAAAAAAAAAAAAIBgAAAAAAAAkGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAwYAAAAAAAADBgAAAAAAAAAAAAAAAPC/" - ], - [ - 799, - 1, - "insert", - { - "characters": "ment." - }, - "BQAAAAkGAAAAAAAACgYAAAAAAAAAAAAACgYAAAAAAAALBgAAAAAAAAAAAAALBgAAAAAAAAwGAAAAAAAAAAAAAAwGAAAAAAAADQYAAAAAAAAAAAAADQYAAAAAAAAOBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACQYAAAAAAAAJBgAAAAAAAAAAAAAAAPC/" - ], - [ - 800, - 1, - "insert", - { - "characters": "id" - }, - "AgAAAA4GAAAAAAAADwYAAAAAAAAAAAAADwYAAAAAAAAQBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADgYAAAAAAAAOBgAAAAAAAAAAAAAAAPC/" - ], - [ - 801, - 1, - "insert", - { - "characters": "," - }, - "AQAAABAGAAAAAAAAEQYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEAYAAAAAAAAQBgAAAAAAAAAAAAAAAPC/" - ], - [ - 802, + 239, 1, "insert", { "characters": " " }, - "AQAAABEGAAAAAAAAEgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEQYAAAAAAAARBgAAAAAAAAAAAAAAAPC/" + "AQAAABIBAAAAAAAAEwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" ], [ - 803, + 240, 1, "insert", { - "characters": "url=" + "characters": "\nand" }, - "BAAAABIGAAAAAAAAEwYAAAAAAAAAAAAAEwYAAAAAAAAUBgAAAAAAAAAAAAAUBgAAAAAAABUGAAAAAAAAAAAAABUGAAAAAAAAFgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgYAAAAAAAASBgAAAAAAAAAAAAAAAPC/" + "BQAAABMBAAAAAAAAFAEAAAAAAAAAAAAAFAEAAAAAAAAYAQAAAAAAAAAAAAAYAQAAAAAAABkBAAAAAAAAAAAAABkBAAAAAAAAGgEAAAAAAAAAAAAAGgEAAAAAAAAbAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" ], [ - 804, + 241, 1, "insert", { "characters": " " }, - "AQAAABYGAAAAAAAAFwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" + "AQAAABsBAAAAAAAAHAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGwEAAAAAAAAbAQAAAAAAAAAAAAAAAPC/" ], [ - 805, + 244, + 1, + "insert", + { + "characters": "comment." + }, + "CAAAABwBAAAAAAAAHQEAAAAAAAAAAAAAHQEAAAAAAAAeAQAAAAAAAAAAAAAeAQAAAAAAAB8BAAAAAAAAAAAAAB8BAAAAAAAAIAEAAAAAAAAAAAAAIAEAAAAAAAAhAQAAAAAAAAAAAAAhAQAAAAAAACIBAAAAAAAAAAAAACIBAAAAAAAAIwEAAAAAAAAAAAAAIwEAAAAAAAAkAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHAEAAAAAAAAcAQAAAAAAAAAAAAAAAPC/" + ], + [ + 248, + 1, + "insert", + { + "characters": "\nfrom" + }, + "BQAAAHYAAAAAAAAAdwAAAAAAAAAAAAAAdwAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdgAAAAAAAAB2AAAAAAAAAAAAAAAAAPC/" + ], + [ + 249, + 1, + "insert", + { + "characters": " stacoys" + }, + "CAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAACAAAAAAAAAAIEAAAAAAAAAAAAAAIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" + ], + [ + 250, + 3, + "left_delete", + null, + "AwAAAIIAAAAAAAAAggAAAAAAAAABAAAAc4EAAAAAAAAAgQAAAAAAAAABAAAAeYAAAAAAAAAAgAAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" + ], + [ + 251, + 1, + "insert", + { + "characters": "osys.co" + }, + "BwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" + ], + [ + 252, + 1, + "insert", + { + "characters": "mm" + }, + "AgAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 253, + 3, + "left_delete", + null, + "AwAAAIgAAAAAAAAAiAAAAAAAAAABAAAAbYcAAAAAAAAAhwAAAAAAAAABAAAAbYYAAAAAAAAAhgAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAPC/" + ], + [ + 254, + 1, + "insert", + { + "characters": "m" + }, + "AQAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" + ], + [ + 255, + 2, + "left_delete", + null, + "AgAAAIYAAAAAAAAAhgAAAAAAAAABAAAAbYUAAAAAAAAAhQAAAAAAAAABAAAAYw", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" + ], + [ + 256, + 1, + "insert", + { + "characters": "mop" + }, + "AwAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" + ], + [ + 257, 1, "left_delete", null, - "AQAAABYGAAAAAAAAFgYAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAFwYAAAAAAAAXBgAAAAAAAAAAAAAAAPC/" + "AQAAAIcAAAAAAAAAhwAAAAAAAAABAAAAcA", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" ], [ - 806, + 258, 1, "insert", { - "characters": "comment" + "characters": "d" }, - "BwAAABYGAAAAAAAAFwYAAAAAAAAAAAAAFwYAAAAAAAAYBgAAAAAAAAAAAAAYBgAAAAAAABkGAAAAAAAAAAAAABkGAAAAAAAAGgYAAAAAAAAAAAAAGgYAAAAAAAAbBgAAAAAAAAAAAAAbBgAAAAAAABwGAAAAAAAAAAAAABwGAAAAAAAAHQYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" + "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" ], [ - 812, - 1, - "insert", - { - "characters": "row" - }, - "BAAAAOYEAAAAAAAA5wQAAAAAAAAAAAAA5wQAAAAAAADnBAAAAAAAAAcAAABjb21tZW505wQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOkEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5gQAAAAAAADtBAAAAAAAAAAAAAAAAPC/" - ], - [ - 817, - 1, - "insert", - { - "characters": "row" - }, - "BAAAAAIGAAAAAAAAAwYAAAAAAAAAAAAAAwYAAAAAAAADBgAAAAAAAAcAAABjb21tZW50AwYAAAAAAAAEBgAAAAAAAAAAAAAEBgAAAAAAAAUGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAgYAAAAAAAAJBgAAAAAAAAAAAAAAAPC/" - ], - [ - 822, - 1, - "insert", - { - "characters": "row.url" - }, - "CAAAAA4GAAAAAAAADwYAAAAAAAAAAAAADwYAAAAAAAAPBgAAAAAAAAcAAABjb21tZW50DwYAAAAAAAAQBgAAAAAAAAAAAAAQBgAAAAAAABEGAAAAAAAAAAAAABEGAAAAAAAAEgYAAAAAAAAAAAAAEgYAAAAAAAATBgAAAAAAAAAAAAATBgAAAAAAABQGAAAAAAAAAAAAABQGAAAAAAAAFQYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADgYAAAAAAAAVBgAAAAAAAAAAAAAAAPC/" - ], - [ - 823, - 1, - "insert", - { - "characters": "," - }, - "AQAAABUGAAAAAAAAFgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFQYAAAAAAAAVBgAAAAAAAAAAAAAAAPC/" - ], - [ - 824, - 1, - "insert", - { - "characters": " auth" - }, - "BQAAABYGAAAAAAAAFwYAAAAAAAAAAAAAFwYAAAAAAAAYBgAAAAAAAAAAAAAYBgAAAAAAABkGAAAAAAAAAAAAABkGAAAAAAAAGgYAAAAAAAAAAAAAGgYAAAAAAAAbBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFgYAAAAAAAAWBgAAAAAAAAAAAAAAAPC/" - ], - [ - 827, + 259, 1, "insert_completion", { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_name\",\"filterText\":\"author_name\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":47},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_name=\"}}", + "completion": "lsp_select_completion_item {\"item\":{\"label\":\"model\",\"sortText\":\"02.9999.model\",\"kind\":9},\"session_name\":\"LSP-pyright\"}", "format": "command", "keep_prefix": false, "must_insert": false, - "trigger": "author_name=" + "trigger": "model" }, - "AgAAABcGAAAAAAAAFwYAAAAAAAAEAAAAYXV0aBcGAAAAAAAAIwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGwYAAAAAAAAbBgAAAAAAAAAAAAAAAPC/" + "AgAAAIUAAAAAAAAAhQAAAAAAAAADAAAAbW9khQAAAAAAAACKAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" ], [ - 828, + 260, 1, "insert", { - "characters": "row.a" + "characters": "i" }, - "BQAAACMGAAAAAAAAJAYAAAAAAAAAAAAAJAYAAAAAAAAlBgAAAAAAAAAAAAAlBgAAAAAAACYGAAAAAAAAAAAAACYGAAAAAAAAJwYAAAAAAAAAAAAAJwYAAAAAAAAoBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIwYAAAAAAAAjBgAAAAAAAAAAAAAAAPC/" + "AQAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" ], [ - 829, + 261, 1, - "insert_completion", - { - "completion": "author_name", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_name" - }, - "AgAAACcGAAAAAAAAJwYAAAAAAAABAAAAYScGAAAAAAAAMgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKAYAAAAAAAAoBgAAAAAAAAAAAAAAAPC/" + "left_delete", + null, + "AQAAAIoAAAAAAAAAigAAAAAAAAABAAAAaQ", + "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" ], [ - 830, + 262, 1, "insert", { - "characters": "," + "characters": " imort" }, - "AQAAADIGAAAAAAAAMwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" + "BgAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAACMAAAAAAAAAI0AAAAAAAAAAAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" ], [ - 831, - 1, - "insert", - { - "characters": " auth" - }, - "BQAAADMGAAAAAAAANAYAAAAAAAAAAAAANAYAAAAAAAA1BgAAAAAAAAAAAAA1BgAAAAAAADYGAAAAAAAAAAAAADYGAAAAAAAANwYAAAAAAAAAAAAANwYAAAAAAAA4BgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMwYAAAAAAAAzBgAAAAAAAAAAAAAAAPC/" - ], - [ - 834, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_site\",\"filterText\":\"author_site\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":76},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_site=\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_site=" - }, - "AgAAADQGAAAAAAAANAYAAAAAAAAEAAAAYXV0aDQGAAAAAAAAQAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOAYAAAAAAAA4BgAAAAAAAAAAAAAAAPC/" - ], - [ - 835, - 1, - "insert", - { - "characters": "=row.au" - }, - "BwAAAEAGAAAAAAAAQQYAAAAAAAAAAAAAQQYAAAAAAABCBgAAAAAAAAAAAABCBgAAAAAAAEMGAAAAAAAAAAAAAEMGAAAAAAAARAYAAAAAAAAAAAAARAYAAAAAAABFBgAAAAAAAAAAAABFBgAAAAAAAEYGAAAAAAAAAAAAAEYGAAAAAAAARwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQAYAAAAAAABABgAAAAAAAAAAAAAAAPC/" - ], - [ - 836, - 1, - "insert_completion", - { - "completion": "author_site", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_site" - }, - "AgAAAEUGAAAAAAAARQYAAAAAAAACAAAAYXVFBgAAAAAAAFAGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARwYAAAAAAABHBgAAAAAAAAAAAAAAAPC/" - ], - [ - 837, - 1, - "insert", - { - "characters": "," - }, - "AQAAAFAGAAAAAAAAUQYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUAYAAAAAAABQBgAAAAAAAAAAAAAAAPC/" - ], - [ - 838, + 263, 1, "insert", { "characters": " " }, - "AQAAAFEGAAAAAAAAUgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQYAAAAAAABRBgAAAAAAAAAAAAAAAPC/" + "AQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" ], [ - 839, + 264, + 4, + "left_delete", + null, + "BAAAAJAAAAAAAAAAkAAAAAAAAAABAAAAII8AAAAAAAAAjwAAAAAAAAABAAAAdI4AAAAAAAAAjgAAAAAAAAABAAAAco0AAAAAAAAAjQAAAAAAAAABAAAAbw", + "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 265, 1, "insert", { - "characters": "au" + "characters": "port" }, - "AgAAAFIGAAAAAAAAUwYAAAAAAAAAAAAAUwYAAAAAAABUBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgYAAAAAAABSBgAAAAAAAAAAAAAAAPC/" + "BAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" ], [ - 842, + 266, + 1, + "insert", + { + "characters": " " + }, + "AQAAAJEAAAAAAAAAkgAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" + ], + [ + 267, 1, "insert_completion", { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"04.9999.author_gravatar\",\"filterText\":\"author_gravatar\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":63,\"character\":104},\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"author_gravatar=\"}}", + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"comment\",\"position\":{\"line\":7,\"character\":27},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"comment\",\"sortText\":\"09.9999.comment\",\"kind\":9},\"session_name\":\"LSP-pyright\"}", "format": "command", "keep_prefix": false, - "must_insert": false, - "trigger": "author_gravatar=" + "must_insert": true, + "trigger": "comment" }, - "AgAAAFIGAAAAAAAAUgYAAAAAAAACAAAAYXVSBgAAAAAAAGIGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAYAAAAAAABUBgAAAAAAAAAAAAAAAPC/" + "AQAAAJIAAAAAAAAAmQAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" ], [ - 843, + 274, 1, "insert", { - "characters": "row.a" + "characters": ".comment" }, - "BQAAAGIGAAAAAAAAYwYAAAAAAAAAAAAAYwYAAAAAAABkBgAAAAAAAAAAAABkBgAAAAAAAGUGAAAAAAAAAAAAAGUGAAAAAAAAZgYAAAAAAAAAAAAAZgYAAAAAAABnBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYgYAAAAAAABiBgAAAAAAAAAAAAAAAPC/" + "CAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAACMAAAAAAAAAI0AAAAAAAAAAAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACSAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" ], [ - 844, + 280, + 1, + "insert", + { + "characters": "C" + }, + "AgAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACbAAAAAAAAAAEAAABj", + "AQAAAAAAAAABAAAAmwAAAAAAAACaAAAAAAAAAAAAAAAAAPC/" + ], + [ + 284, + 1, + "insert", + { + "characters": " :" + }, + "AgAAABIBAAAAAAAAEwEAAAAAAAAAAAAAEwEAAAAAAAAUAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" + ], + [ + 285, + 1, + "insert", + { + "characters": " Comment" + }, + "CAAAABQBAAAAAAAAFQEAAAAAAAAAAAAAFQEAAAAAAAAWAQAAAAAAAAAAAAAWAQAAAAAAABcBAAAAAAAAAAAAABcBAAAAAAAAGAEAAAAAAAAAAAAAGAEAAAAAAAAZAQAAAAAAAAAAAAAZAQAAAAAAABoBAAAAAAAAAAAAABoBAAAAAAAAGwEAAAAAAAAAAAAAGwEAAAAAAAAcAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAFAEAAAAAAAAUAQAAAAAAAAAAAAAAAPC/" + ], + [ + 289, + 1, + "right_delete", + null, + "AQAAAEkBAAAAAAAASQEAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAATQEAAAAAAABJAQAAAAAAAAAAAAAAAAAA" + ], + [ + 290, + 1, + "left_delete", + null, + "AQAAAEgBAAAAAAAASAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" + ], + [ + 293, + 1, + "left_delete", + null, + "AQAAAFMBAAAAAAAAUwEAAAAAAAABAAAALg", + "AQAAAAAAAAABAAAAVAEAAAAAAABUAQAAAAAAAAAAAAAAAPC/" + ], + [ + 294, + 1, + "insert", + { + "characters": "." + }, + "AQAAAFMBAAAAAAAAVAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUwEAAAAAAABTAQAAAAAAAAAAAAAAAPC/" + ], + [ + 295, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_gravatar\",\"position\":{\"line\":14,\"character\":46},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_gravatar\",\"sortText\":\"09.9999.author_gravatar\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "author_gravatar" + }, + "AQAAAFQBAAAAAAAAYwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAEAAAAAAABUAQAAAAAAAAAAAAAAAPC/" + ], + [ + 296, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAAGMBAAAAAAAAZAEAAAAAAAAAAAAAZAEAAAAAAABlAQAAAAAAAAAAAABlAQAAAAAAAGYBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYwEAAAAAAABjAQAAAAAAAAAAAAAAAPC/" + ], + [ + 297, + 1, + "insert", + { + "characters": " other.a" + }, + "CAAAAGYBAAAAAAAAZwEAAAAAAAAAAAAAZwEAAAAAAABoAQAAAAAAAAAAAABoAQAAAAAAAGkBAAAAAAAAAAAAAGkBAAAAAAAAagEAAAAAAAAAAAAAagEAAAAAAABrAQAAAAAAAAAAAABrAQAAAAAAAGwBAAAAAAAAAAAAAGwBAAAAAAAAbQEAAAAAAAAAAAAAbQEAAAAAAABuAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAZgEAAAAAAABmAQAAAAAAAAAAAAAAAPC/" + ], + [ + 298, 1, "insert_completion", { @@ -5616,171 +4787,305 @@ "must_insert": false, "trigger": "author_gravatar" }, - "AgAAAGYGAAAAAAAAZgYAAAAAAAABAAAAYWYGAAAAAAAAdQYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZwYAAAAAAABnBgAAAAAAAAAAAAAAAPC/" + "AgAAAG0BAAAAAAAAbQEAAAAAAAABAAAAYW0BAAAAAAAAfAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" ], [ - 845, + 299, 1, "insert", { - "characters": "," + "characters": " and" }, - "AQAAAHUGAAAAAAAAdgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdQYAAAAAAAB1BgAAAAAAAAAAAAAAAPC/" + "BAAAAHwBAAAAAAAAfQEAAAAAAAAAAAAAfQEAAAAAAAB+AQAAAAAAAAAAAAB+AQAAAAAAAH8BAAAAAAAAAAAAAH8BAAAAAAAAgAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfAEAAAAAAAB8AQAAAAAAAAAAAAAAAPC/" ], [ - 846, + 300, 1, "insert", { - "characters": " " + "characters": " comment." }, - "AQAAAHYGAAAAAAAAdwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgYAAAAAAAB2BgAAAAAAAAAAAAAAAPC/" + "CQAAAIABAAAAAAAAgQEAAAAAAAAAAAAAgQEAAAAAAACCAQAAAAAAAAAAAACCAQAAAAAAAIMBAAAAAAAAAAAAAIMBAAAAAAAAhAEAAAAAAAAAAAAAhAEAAAAAAACFAQAAAAAAAAAAAACFAQAAAAAAAIYBAAAAAAAAAAAAAIYBAAAAAAAAhwEAAAAAAAAAAAAAhwEAAAAAAACIAQAAAAAAAAAAAACIAQAAAAAAAIkBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAAPC/" ], [ - 847, + 302, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_name\",\"position\":{\"line\":14,\"character\":99},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_name\",\"sortText\":\"09.9999.author_name\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "author_name" + }, + "AQAAAIkBAAAAAAAAlAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiQEAAAAAAACJAQAAAAAAAAAAAAAAAPC/" + ], + [ + 303, 1, "insert", { - "characters": "contnt" + "characters": " ==" }, - "BgAAAHcGAAAAAAAAeAYAAAAAAAAAAAAAeAYAAAAAAAB5BgAAAAAAAAAAAAB5BgAAAAAAAHoGAAAAAAAAAAAAAHoGAAAAAAAAewYAAAAAAAAAAAAAewYAAAAAAAB8BgAAAAAAAAAAAAB8BgAAAAAAAH0GAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdwYAAAAAAAB3BgAAAAAAAAAAAAAAAPC/" + "AwAAAJQBAAAAAAAAlQEAAAAAAAAAAAAAlQEAAAAAAACWAQAAAAAAAAAAAACWAQAAAAAAAJcBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlAEAAAAAAACUAQAAAAAAAAAAAAAAAPC/" ], [ - 848, - 2, - "left_delete", - null, - "AgAAAHwGAAAAAAAAfAYAAAAAAAABAAAAdHsGAAAAAAAAewYAAAAAAAABAAAAbg", - "AQAAAAAAAAABAAAAfQYAAAAAAAB9BgAAAAAAAAAAAAAAAPC/" - ], - [ - 849, + 304, 1, "insert", { - "characters": "ent=" + "characters": " other." }, - "BAAAAHsGAAAAAAAAfAYAAAAAAAAAAAAAfAYAAAAAAAB9BgAAAAAAAAAAAAB9BgAAAAAAAH4GAAAAAAAAAAAAAH4GAAAAAAAAfwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAewYAAAAAAAB7BgAAAAAAAAAAAAAAAPC/" + "BwAAAJcBAAAAAAAAmAEAAAAAAAAAAAAAmAEAAAAAAACZAQAAAAAAAAAAAACZAQAAAAAAAJoBAAAAAAAAAAAAAJoBAAAAAAAAmwEAAAAAAAAAAAAAmwEAAAAAAACcAQAAAAAAAAAAAACcAQAAAAAAAJ0BAAAAAAAAAAAAAJ0BAAAAAAAAngEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAlwEAAAAAAACXAQAAAAAAAAAAAAAAAPC/" ], [ - 850, - 8, - "left_delete", - null, - "CAAAAH4GAAAAAAAAfgYAAAAAAAABAAAAPX0GAAAAAAAAfQYAAAAAAAABAAAAdHwGAAAAAAAAfAYAAAAAAAABAAAAbnsGAAAAAAAAewYAAAAAAAABAAAAZXoGAAAAAAAAegYAAAAAAAABAAAAdHkGAAAAAAAAeQYAAAAAAAABAAAAbngGAAAAAAAAeAYAAAAAAAABAAAAb3cGAAAAAAAAdwYAAAAAAAABAAAAYw", - "AQAAAAAAAAABAAAAfwYAAAAAAAB/BgAAAAAAAAAAAAAAAPC/" - ], - [ - 851, + 305, 1, "insert", { - "characters": "content=row" + "characters": "a" }, - "CwAAAHcGAAAAAAAAeAYAAAAAAAAAAAAAeAYAAAAAAAB5BgAAAAAAAAAAAAB5BgAAAAAAAHoGAAAAAAAAAAAAAHoGAAAAAAAAewYAAAAAAAAAAAAAewYAAAAAAAB8BgAAAAAAAAAAAAB8BgAAAAAAAH0GAAAAAAAAAAAAAH0GAAAAAAAAfgYAAAAAAAAAAAAAfgYAAAAAAAB/BgAAAAAAAAAAAAB/BgAAAAAAAIAGAAAAAAAAAAAAAIAGAAAAAAAAgQYAAAAAAAAAAAAAgQYAAAAAAACCBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdwYAAAAAAAB3BgAAAAAAAAAAAAAAAPC/" + "AQAAAJ4BAAAAAAAAnwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAngEAAAAAAACeAQAAAAAAAAAAAAAAAPC/" ], [ - 852, + 306, + 1, + "insert_completion", + { + "completion": "author_name", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_name" + }, + "AgAAAJ4BAAAAAAAAngEAAAAAAAABAAAAYZ4BAAAAAAAAqQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAnwEAAAAAAACfAQAAAAAAAAAAAAAAAPC/" + ], + [ + 307, 1, "insert", { - "characters": "." + "characters": " and" }, - "AQAAAIIGAAAAAAAAgwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAggYAAAAAAACCBgAAAAAAAAAAAAAAAPC/" + "BAAAAKkBAAAAAAAAqgEAAAAAAAAAAAAAqgEAAAAAAACrAQAAAAAAAAAAAACrAQAAAAAAAKwBAAAAAAAAAAAAAKwBAAAAAAAArQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqQEAAAAAAACpAQAAAAAAAAAAAAAAAPC/" ], [ - 853, + 308, + 1, + "insert", + { + "characters": " comment." + }, + "CQAAAK0BAAAAAAAArgEAAAAAAAAAAAAArgEAAAAAAACvAQAAAAAAAAAAAACvAQAAAAAAALABAAAAAAAAAAAAALABAAAAAAAAsQEAAAAAAAAAAAAAsQEAAAAAAACyAQAAAAAAAAAAAACyAQAAAAAAALMBAAAAAAAAAAAAALMBAAAAAAAAtAEAAAAAAAAAAAAAtAEAAAAAAAC1AQAAAAAAAAAAAAC1AQAAAAAAALYBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAArQEAAAAAAACtAQAAAAAAAAAAAAAAAPC/" + ], + [ + 311, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_site\",\"position\":{\"line\":14,\"character\":144},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_site\",\"sortText\":\"09.9999.author_site\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "author_site" + }, + "AQAAALYBAAAAAAAAwQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtgEAAAAAAAC2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 312, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAAMEBAAAAAAAAwgEAAAAAAAAAAAAAwgEAAAAAAADDAQAAAAAAAAAAAADDAQAAAAAAAMQBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" + ], + [ + 313, + 1, + "insert", + { + "characters": " other." + }, + "BwAAAMQBAAAAAAAAxQEAAAAAAAAAAAAAxQEAAAAAAADGAQAAAAAAAAAAAADGAQAAAAAAAMcBAAAAAAAAAAAAAMcBAAAAAAAAyAEAAAAAAAAAAAAAyAEAAAAAAADJAQAAAAAAAAAAAADJAQAAAAAAAMoBAAAAAAAAAAAAAMoBAAAAAAAAywEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxAEAAAAAAADEAQAAAAAAAAAAAAAAAPC/" + ], + [ + 314, + 1, + "insert", + { + "characters": "ai" + }, + "AgAAAMsBAAAAAAAAzAEAAAAAAAAAAAAAzAEAAAAAAADNAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAywEAAAAAAADLAQAAAAAAAAAAAAAAAPC/" + ], + [ + 315, + 1, + "insert_completion", + { + "completion": "author_site", + "format": "text", + "keep_prefix": false, + "must_insert": false, + "trigger": "author_site" + }, + "AgAAAMsBAAAAAAAAywEAAAAAAAACAAAAYWnLAQAAAAAAANYBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAzQEAAAAAAADNAQAAAAAAAAAAAAAAAPC/" + ], + [ + 316, + 1, + "insert", + { + "characters": " and" + }, + "BAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA1wEAAAAAAADYAQAAAAAAAAAAAADYAQAAAAAAANkBAAAAAAAAAAAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1gEAAAAAAADWAQAAAAAAAAAAAAAAAPC/" + ], + [ + 317, + 1, + "insert", + { + "characters": " comment." + }, + "CQAAANoBAAAAAAAA2wEAAAAAAAAAAAAA2wEAAAAAAADcAQAAAAAAAAAAAADcAQAAAAAAAN0BAAAAAAAAAAAAAN0BAAAAAAAA3gEAAAAAAAAAAAAA3gEAAAAAAADfAQAAAAAAAAAAAADfAQAAAAAAAOABAAAAAAAAAAAAAOABAAAAAAAA4QEAAAAAAAAAAAAA4QEAAAAAAADiAQAAAAAAAAAAAADiAQAAAAAAAOMBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gEAAAAAAADaAQAAAAAAAAAAAAAAAPC/" + ], + [ + 323, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"content\",\"position\":{\"line\":14,\"character\":189},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"content\",\"sortText\":\"09.9999.content\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "content" + }, + "AQAAAOMBAAAAAAAA6gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4wEAAAAAAADjAQAAAAAAAAAAAAAAAPC/" + ], + [ + 324, + 1, + "insert", + { + "characters": " =" + }, + "AgAAAOoBAAAAAAAA6wEAAAAAAAAAAAAA6wEAAAAAAADsAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6gEAAAAAAADqAQAAAAAAAAAAAAAAAPC/" + ], + [ + 325, + 1, + "insert", + { + "characters": "=" + }, + "AQAAAOwBAAAAAAAA7QEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7AEAAAAAAADsAQAAAAAAAAAAAAAAAPC/" + ], + [ + 326, + 1, + "insert", + { + "characters": " other." + }, + "BwAAAO0BAAAAAAAA7gEAAAAAAAAAAAAA7gEAAAAAAADvAQAAAAAAAAAAAADvAQAAAAAAAPABAAAAAAAAAAAAAPABAAAAAAAA8QEAAAAAAAAAAAAA8QEAAAAAAADyAQAAAAAAAAAAAADyAQAAAAAAAPMBAAAAAAAAAAAAAPMBAAAAAAAA9AEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA7QEAAAAAAADtAQAAAAAAAAAAAAAAAPC/" + ], + [ + 327, 1, "insert", { "characters": "content" }, - "BwAAAIMGAAAAAAAAhAYAAAAAAAAAAAAAhAYAAAAAAACFBgAAAAAAAAAAAACFBgAAAAAAAIYGAAAAAAAAAAAAAIYGAAAAAAAAhwYAAAAAAAAAAAAAhwYAAAAAAACIBgAAAAAAAAAAAACIBgAAAAAAAIkGAAAAAAAAAAAAAIkGAAAAAAAAigYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwYAAAAAAACDBgAAAAAAAAAAAAAAAPC/" + "BwAAAPQBAAAAAAAA9QEAAAAAAAAAAAAA9QEAAAAAAAD2AQAAAAAAAAAAAAD2AQAAAAAAAPcBAAAAAAAAAAAAAPcBAAAAAAAA+AEAAAAAAAAAAAAA+AEAAAAAAAD5AQAAAAAAAAAAAAD5AQAAAAAAAPoBAAAAAAAAAAAAAPoBAAAAAAAA+wEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA9AEAAAAAAAD0AQAAAAAAAAAAAAAAAPC/" ], [ - 861, - 1, - "left_delete", - null, - "AQAAAEAGAAAAAAAAQAYAAAAAAAABAAAAPQ", - "AQAAAAAAAAABAAAAQQYAAAAAAABBBgAAAAAAAAAAAAAAAPC/" - ], - [ - 865, + 328, 1, "insert", { - "characters": "\n" + "characters": " and" }, - "BAAAAIkGAAAAAAAAigYAAAAAAAAAAAAAigYAAAAAAACOBgAAAAAAAAAAAACKBgAAAAAAAIoGAAAAAAAABAAAACAgICCKBgAAAAAAAJIGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQYAAAAAAACJBgAAAAAAAAAAAAAAAPC/" + "BAAAAPsBAAAAAAAA/AEAAAAAAAAAAAAA/AEAAAAAAAD9AQAAAAAAAAAAAAD9AQAAAAAAAP4BAAAAAAAAAAAAAP4BAAAAAAAA/wEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA+wEAAAAAAAD7AQAAAAAAAAAAAAAAAPC/" ], [ - 868, + 329, 1, "insert", { - "characters": "\n," + "characters": " comment." }, - "BQAAAIkGAAAAAAAAigYAAAAAAAAAAAAAigYAAAAAAACOBgAAAAAAAAAAAACKBgAAAAAAAIoGAAAAAAAABAAAACAgICCKBgAAAAAAAJIGAAAAAAAAAAAAAJIGAAAAAAAAkwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQYAAAAAAACJBgAAAAAAAP///////+9/" + "CQAAAP8BAAAAAAAAAAIAAAAAAAAAAAAAAAIAAAAAAAABAgAAAAAAAAAAAAABAgAAAAAAAAICAAAAAAAAAAAAAAICAAAAAAAAAwIAAAAAAAAAAAAAAwIAAAAAAAAEAgAAAAAAAAAAAAAEAgAAAAAAAAUCAAAAAAAAAAAAAAUCAAAAAAAABgIAAAAAAAAAAAAABgIAAAAAAAAHAgAAAAAAAAAAAAAHAgAAAAAAAAgCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA/wEAAAAAAAD/AQAAAAAAAAAAAAAAAPC/" ], [ - 869, + 334, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"created\",\"position\":{\"line\":14,\"character\":226},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"created\",\"sortText\":\"09.9999.created\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "created" + }, + "AQAAAAgCAAAAAAAADwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACAIAAAAAAAAIAgAAAAAAAAAAAAAAAPC/" + ], + [ + 335, 1, "insert", { - "characters": " " + "characters": " ==" }, - "AQAAAJMGAAAAAAAAlAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkwYAAAAAAACTBgAAAAAAAAAAAAAAAPC/" + "AwAAAA8CAAAAAAAAEAIAAAAAAAAAAAAAEAIAAAAAAAARAgAAAAAAAAAAAAARAgAAAAAAABICAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADwIAAAAAAAAPAgAAAAAAAAAAAAAAAPC/" ], [ - 879, - 1, - "paste", - null, - "AQAAAJQGAAAAAAAAmwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlAYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" - ], - [ - 881, - 1, - "right_delete", - null, - "AQAAAJQGAAAAAAAAlAYAAAAAAAAHAAAAY3JlYXRlZA", - "AQAAAAAAAAABAAAAmwYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" - ], - [ - 882, + 336, 1, "insert", { - "characters": "created=" + "characters": " other.c" }, - "CAAAAJQGAAAAAAAAlQYAAAAAAAAAAAAAlQYAAAAAAACWBgAAAAAAAAAAAACWBgAAAAAAAJcGAAAAAAAAAAAAAJcGAAAAAAAAmAYAAAAAAAAAAAAAmAYAAAAAAACZBgAAAAAAAAAAAACZBgAAAAAAAJoGAAAAAAAAAAAAAJoGAAAAAAAAmwYAAAAAAAAAAAAAmwYAAAAAAACcBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlAYAAAAAAACUBgAAAAAAAAAAAAAAAPC/" + "CAAAABICAAAAAAAAEwIAAAAAAAAAAAAAEwIAAAAAAAAUAgAAAAAAAAAAAAAUAgAAAAAAABUCAAAAAAAAAAAAABUCAAAAAAAAFgIAAAAAAAAAAAAAFgIAAAAAAAAXAgAAAAAAAAAAAAAXAgAAAAAAABgCAAAAAAAAAAAAABgCAAAAAAAAGQIAAAAAAAAAAAAAGQIAAAAAAAAaAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEgIAAAAAAAASAgAAAAAAAAAAAAAAAPC/" ], [ - 883, + 337, 1, "insert", { - "characters": "row.c" + "characters": "re" }, - "BQAAAJwGAAAAAAAAnQYAAAAAAAAAAAAAnQYAAAAAAACeBgAAAAAAAAAAAACeBgAAAAAAAJ8GAAAAAAAAAAAAAJ8GAAAAAAAAoAYAAAAAAAAAAAAAoAYAAAAAAAChBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAnAYAAAAAAACcBgAAAAAAAAAAAAAAAPC/" + "AgAAABoCAAAAAAAAGwIAAAAAAAAAAAAAGwIAAAAAAAAcAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAGgIAAAAAAAAaAgAAAAAAAAAAAAAAAPC/" ], [ - 884, + 338, 1, "insert_completion", { @@ -5790,59 +5095,75 @@ "must_insert": false, "trigger": "created" }, - "AgAAAKAGAAAAAAAAoAYAAAAAAAABAAAAY6AGAAAAAAAApwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoQYAAAAAAAChBgAAAAAAAAAAAAAAAPC/" + "AgAAABkCAAAAAAAAGQIAAAAAAAADAAAAY3JlGQIAAAAAAAAgAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAHAIAAAAAAAAcAgAAAAAAAAAAAAAAAPC/" ], [ - 885, + 339, 1, "insert", { - "characters": "," + "characters": " and" }, - "AQAAAKcGAAAAAAAAqAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApwYAAAAAAACnBgAAAAAAAAAAAAAAAPC/" + "BAAAACACAAAAAAAAIQIAAAAAAAAAAAAAIQIAAAAAAAAiAgAAAAAAAAAAAAAiAgAAAAAAACMCAAAAAAAAAAAAACMCAAAAAAAAJAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIAIAAAAAAAAgAgAAAAAAAAAAAAAAAPC/" ], [ - 886, + 340, 1, "insert", { - "characters": " no" + "characters": " comment." }, - "AwAAAKgGAAAAAAAAqQYAAAAAAAAAAAAAqQYAAAAAAACqBgAAAAAAAAAAAACqBgAAAAAAAKsGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqAYAAAAAAACoBgAAAAAAAAAAAAAAAPC/" + "CQAAACQCAAAAAAAAJQIAAAAAAAAAAAAAJQIAAAAAAAAmAgAAAAAAAAAAAAAmAgAAAAAAACcCAAAAAAAAAAAAACcCAAAAAAAAKAIAAAAAAAAAAAAAKAIAAAAAAAApAgAAAAAAAAAAAAApAgAAAAAAACoCAAAAAAAAAAAAACoCAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAAsAgAAAAAAAAAAAAAsAgAAAAAAAC0CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJAIAAAAAAAAkAgAAAAAAAAAAAAAAAPC/" ], [ - 887, + 347, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"notified\",\"position\":{\"line\":14,\"character\":263},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"notified\",\"sortText\":\"09.9999.notified\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": true, + "trigger": "notified" + }, + "AQAAAC0CAAAAAAAANQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALQIAAAAAAAAtAgAAAAAAAAAAAAAAAPC/" + ], + [ + 348, 1, "insert", { - "characters": "tified=o" + "characters": " ==" }, - "CAAAAKsGAAAAAAAArAYAAAAAAAAAAAAArAYAAAAAAACtBgAAAAAAAAAAAACtBgAAAAAAAK4GAAAAAAAAAAAAAK4GAAAAAAAArwYAAAAAAAAAAAAArwYAAAAAAACwBgAAAAAAAAAAAACwBgAAAAAAALEGAAAAAAAAAAAAALEGAAAAAAAAsgYAAAAAAAAAAAAAsgYAAAAAAACzBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAPC/" + "AwAAADUCAAAAAAAANgIAAAAAAAAAAAAANgIAAAAAAAA3AgAAAAAAAAAAAAA3AgAAAAAAADgCAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAPC/" ], [ - 888, - 1, - "left_delete", - null, - "AQAAALIGAAAAAAAAsgYAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAswYAAAAAAACzBgAAAAAAAAAAAAAAAPC/" - ], - [ - 889, + 349, 1, "insert", { - "characters": "row.n" + "characters": " other" }, - "BQAAALIGAAAAAAAAswYAAAAAAAAAAAAAswYAAAAAAAC0BgAAAAAAAAAAAAC0BgAAAAAAALUGAAAAAAAAAAAAALUGAAAAAAAAtgYAAAAAAAAAAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAsgYAAAAAAACyBgAAAAAAAAAAAAAAAPC/" + "BgAAADgCAAAAAAAAOQIAAAAAAAAAAAAAOQIAAAAAAAA6AgAAAAAAAAAAAAA6AgAAAAAAADsCAAAAAAAAAAAAADsCAAAAAAAAPAIAAAAAAAAAAAAAPAIAAAAAAAA9AgAAAAAAAAAAAAA9AgAAAAAAAD4CAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" ], [ - 890, + 350, + 1, + "insert", + { + "characters": ".noti" + }, + "BQAAAD4CAAAAAAAAPwIAAAAAAAAAAAAAPwIAAAAAAABAAgAAAAAAAAAAAABAAgAAAAAAAEECAAAAAAAAAAAAAEECAAAAAAAAQgIAAAAAAAAAAAAAQgIAAAAAAABDAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAPgIAAAAAAAA+AgAAAAAAAAAAAAAAAPC/" + ], + [ + 351, 1, "insert_completion", { @@ -5852,59 +5173,83 @@ "must_insert": false, "trigger": "notified" }, - "AgAAALYGAAAAAAAAtgYAAAAAAAABAAAAbrYGAAAAAAAAvgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtwYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" + "AgAAAD8CAAAAAAAAPwIAAAAAAAAEAAAAbm90aT8CAAAAAAAARwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAQwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" ], [ - 891, + 352, 1, "insert", { - "characters": "," + "characters": " and" }, - "AQAAAL4GAAAAAAAAvwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgYAAAAAAAC+BgAAAAAAAAAAAAAAAPC/" + "BAAAAEcCAAAAAAAASAIAAAAAAAAAAAAASAIAAAAAAABJAgAAAAAAAAAAAABJAgAAAAAAAEoCAAAAAAAAAAAAAEoCAAAAAAAASwIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAARwIAAAAAAABHAgAAAAAAAAAAAAAAAPC/" ], [ - 892, + 353, 1, "insert", { - "characters": " pul" + "characters": " comment" }, - "BAAAAL8GAAAAAAAAwAYAAAAAAAAAAAAAwAYAAAAAAADBBgAAAAAAAAAAAADBBgAAAAAAAMIGAAAAAAAAAAAAAMIGAAAAAAAAwwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvwYAAAAAAAC/BgAAAAAAAAAAAAAAAPC/" + "CAAAAEsCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABNAgAAAAAAAAAAAABNAgAAAAAAAE4CAAAAAAAAAAAAAE4CAAAAAAAATwIAAAAAAAAAAAAATwIAAAAAAABQAgAAAAAAAAAAAABQAgAAAAAAAFECAAAAAAAAAAAAAFECAAAAAAAAUgIAAAAAAAAAAAAAUgIAAAAAAABTAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASwIAAAAAAABLAgAAAAAAAAAAAAAAAPC/" ], [ - 893, - 1, - "left_delete", - null, - "AQAAAMIGAAAAAAAAwgYAAAAAAAABAAAAbA", - "AQAAAAAAAAABAAAAwwYAAAAAAADDBgAAAAAAAAAAAAAAAPC/" - ], - [ - 894, - 1, - "insert", - { - "characters": "blished=row" - }, - "CwAAAMIGAAAAAAAAwwYAAAAAAAAAAAAAwwYAAAAAAADEBgAAAAAAAAAAAADEBgAAAAAAAMUGAAAAAAAAAAAAAMUGAAAAAAAAxgYAAAAAAAAAAAAAxgYAAAAAAADHBgAAAAAAAAAAAADHBgAAAAAAAMgGAAAAAAAAAAAAAMgGAAAAAAAAyQYAAAAAAAAAAAAAyQYAAAAAAADKBgAAAAAAAAAAAADKBgAAAAAAAMsGAAAAAAAAAAAAAMsGAAAAAAAAzAYAAAAAAAAAAAAAzAYAAAAAAADNBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwgYAAAAAAADCBgAAAAAAAAAAAAAAAPC/" - ], - [ - 895, + 354, 1, "insert", { "characters": ".p" }, - "AgAAAM0GAAAAAAAAzgYAAAAAAAAAAAAAzgYAAAAAAADPBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzQYAAAAAAADNBgAAAAAAAAAAAAAAAPC/" + "AgAAAFMCAAAAAAAAVAIAAAAAAAAAAAAAVAIAAAAAAABVAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUwIAAAAAAABTAgAAAAAAAAAAAAAAAPC/" ], [ - 896, + 355, + 1, + "insert_completion", + { + "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"published\",\"position\":{\"line\":14,\"character\":303},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"published\",\"sortText\":\"09.9999.published\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", + "format": "command", + "keep_prefix": false, + "must_insert": false, + "trigger": "published" + }, + "AgAAAFQCAAAAAAAAVAIAAAAAAAABAAAAcFQCAAAAAAAAXQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVQIAAAAAAABVAgAAAAAAAAAAAAAAAPC/" + ], + [ + 356, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXQIAAAAAAABdAgAAAAAAAAAAAAAAAPC/" + ], + [ + 357, + 1, + "insert", + { + "characters": " other.puy" + }, + "CgAAAGACAAAAAAAAYQIAAAAAAAAAAAAAYQIAAAAAAABiAgAAAAAAAAAAAABiAgAAAAAAAGMCAAAAAAAAAAAAAGMCAAAAAAAAZAIAAAAAAAAAAAAAZAIAAAAAAABlAgAAAAAAAAAAAABlAgAAAAAAAGYCAAAAAAAAAAAAAGYCAAAAAAAAZwIAAAAAAAAAAAAAZwIAAAAAAABoAgAAAAAAAAAAAABoAgAAAAAAAGkCAAAAAAAAAAAAAGkCAAAAAAAAagIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYAIAAAAAAABgAgAAAAAAAAAAAAAAAPC/" + ], + [ + 358, + 1, + "left_delete", + null, + "AQAAAGkCAAAAAAAAaQIAAAAAAAABAAAAeQ", + "AQAAAAAAAAABAAAAagIAAAAAAABqAgAAAAAAAAAAAAAAAPC/" + ], + [ + 359, 1, "insert_completion", { @@ -5914,2395 +5259,879 @@ "must_insert": false, "trigger": "published" }, - "AgAAAM4GAAAAAAAAzgYAAAAAAAABAAAAcM4GAAAAAAAA1wYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzwYAAAAAAADPBgAAAAAAAAAAAAAAAPC/" + "AgAAAGcCAAAAAAAAZwIAAAAAAAACAAAAcHVnAgAAAAAAAHACAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAaQIAAAAAAABpAgAAAAAAAAAAAAAAAPC/" ], [ - 900, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADiBgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50Lmluc2VydChhc2RpY3QoY29tbWVudCkpCgoKZGVmIHB1Ymxpc2hfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5pbnNlcnQoYXNkaWN0KGNvbW1lbnQpKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KCkKICAgICAgICAuc29ydChsYW1iZGEgcm93OiByb3cucHVibGlzaGVkKQogICAgKQoKCmRlZiBjb3VudF9wdWJsaXNoZWRfY29tbWVudHModXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkpLmNvdW50KCkKICAgICAgICBpZiB1cmwKICAgICAgICBlbHNlIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5jb3VudCgpCiAgICApCgoKZGVmIGNyZWF0ZV9jb21tZW50KHVybCwgYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlLCBhdXRob3JfZ3JhdmF0YXIsIG1lc3NhZ2UpOgogICAgcm93ID0gZGIoKS5jb21tZW50Lmluc2VydCgKICAgICAgICB1cmw9dXJsLAogICAgICAgIGF1dGhvcl9uYW1lPWF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPWF1dGhvcl9zaXRlLAogICAgICAgIGF1dGhvcl9ncmF2YXRhcj1hdXRob3JfZ3JhdmF0YXIsCiAgICAgICAgY29udGVudD1tZXNzYWdlLAogICAgICAgIGNyZWF0ZWQ9ZGF0ZXRpbWUubm93KCksCiAgICAgICAgbm90aWZpZWQ9Tm9uZSwKICAgICAgICBwdWJsaXNoZWQ9Tm9uZSwKICAgICkKCiAgICByZXR1cm4gQ29tbWVudChpZD1yb3cuaWQsIHVybD1yb3cudXJsLCBhdXRob3JfbmFtZT1yb3cuYXV0aG9yX25hbWUsIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyPXJvdy5hdXRob3JfZ3JhdmF0YXIsIGNvbnRlbnQ9cm93LmNvbnRlbnQKICAgICAgICAsIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsIG5vdGlmaWVkPXJvdy5ub3RpZmllZCwgcHVibGlzaGVkPXJvdy5wdWJsaXNoZWQKICAgICAgICApCgAAAAAAAAAAHwcAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gYAAAAAAADiBgAAAAAAAAAAAAAAqIVA" - ], - [ - 907, - 1, - "insert", - { - "characters": "[" - }, - "AQAAAF4BAAAAAAAAXwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXgEAAAAAAABeAQAAAAAAAAAAAAAAAPC/" - ], - [ - 911, - 1, - "insert", - { - "characters": "]" - }, - "AQAAAG4BAAAAAAAAbwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" - ], - [ - 915, - 1, - "insert", - { - "characters": "[" - }, - "AQAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2QEAAAAAAADZAQAAAAAAAAAAAAAAAPC/" - ], - [ - 921, - 1, - "insert", - { - "characters": "]" - }, - "AQAAAOkBAAAAAAAA6gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6QEAAAAAAADpAQAAAAAAAAAAAAAAAPC/" - ], - [ - 935, - 1, - "insert", - { - "characters": "bulk-" - }, - "BQAAAFcBAAAAAAAAWAEAAAAAAAAAAAAAWAEAAAAAAABZAQAAAAAAAAAAAABZAQAAAAAAAFoBAAAAAAAAAAAAAFoBAAAAAAAAWwEAAAAAAAAAAAAAWwEAAAAAAABcAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVwEAAAAAAABXAQAAAAAAAAAAAAAAAPC/" - ], - [ - 936, - 1, - "left_delete", - null, - "AQAAAFsBAAAAAAAAWwEAAAAAAAABAAAALQ", - "AQAAAAAAAAABAAAAXAEAAAAAAABcAQAAAAAAAAAAAAAAAPC/" - ], - [ - 937, - 1, - "insert", - { - "characters": "_" - }, - "AQAAAFsBAAAAAAAAXAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWwEAAAAAAABbAQAAAAAAAAAAAAAAAPC/" - ], - [ - 949, - 1, - "paste", - null, - "AgAAANcBAAAAAAAA4gEAAAAAAAAAAAAA4gEAAAAAAADiAQAAAAAAAAYAAABpbnNlcnQ", - "AQAAAAAAAAABAAAA1wEAAAAAAADdAQAAAAAAAAAAAAAAAPC/" - ], - [ - 952, - 1, - "insert", - { - "characters": "\ndb" - }, - "BAAAAHUBAAAAAAAAdgEAAAAAAAAAAAAAdgEAAAAAAAB6AQAAAAAAAAAAAAB6AQAAAAAAAHsBAAAAAAAAAAAAAHsBAAAAAAAAfAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdQEAAAAAAAB1AQAAAAAAAAAAAAAAAPC/" - ], - [ - 953, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAHwBAAAAAAAAfgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfAEAAAAAAAB8AQAAAAAAAAAAAAAAAPC/" - ], - [ - 954, - 1, - "insert", - { - "characters": "à." - }, - "AgAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfQEAAAAAAAB9AQAAAAAAAAAAAAAAAPC/" - ], - [ - 955, - 2, - "left_delete", - null, - "AgAAAH4BAAAAAAAAfgEAAAAAAAABAAAALn0BAAAAAAAAfQEAAAAAAAACAAAAw6A", - "AQAAAAAAAAABAAAAfwEAAAAAAAB/AQAAAAAAAAAAAAAAAPC/" - ], - [ - 957, - 1, - "insert", - { - "characters": ".co" - }, - "AwAAAH4BAAAAAAAAfwEAAAAAAAAAAAAAfwEAAAAAAACAAQAAAAAAAAAAAACAAQAAAAAAAIEBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgEAAAAAAAB+AQAAAAAAAAAAAAAAAPC/" - ], - [ - 958, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":2,\"sortText\":\"09.9999.commit\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/dao.py\",\"position\":{\"line\":16,\"character\":11},\"symbolLabel\":\"commit\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"commit\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "commit" - }, - "AgAAAH8BAAAAAAAAfwEAAAAAAAACAAAAY29/AQAAAAAAAIUBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQEAAAAAAACBAQAAAAAAAAAAAAAAAPC/" - ], - [ - 959, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAIUBAAAAAAAAhwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQEAAAAAAACFAQAAAAAAAAAAAAAAAPC/" - ], - [ - 970, - 1, - "reindent", - null, - "AQAAAAgCAAAAAAAADAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACAIAAAAAAAAIAgAAAAAAAAAAAAAAAAAA" - ], - [ - 971, - 1, - "paste", - null, - "AQAAAAwCAAAAAAAAGQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADAIAAAAAAAAMAgAAAAAAAAAAAAAAAPC/" - ], - [ - 976, + 362, 1, "insert", { "characters": "\n" }, - "AgAAAHECAAAAAAAAcgIAAAAAAAAAAAAAcgIAAAAAAAB2AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" + "AgAAAHACAAAAAAAAcQIAAAAAAAAAAAAAcQIAAAAAAAB1AgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAcAIAAAAAAABwAgAAAAAAAP///////+9/" ], [ - 977, - 1, - "paste", - null, - "AQAAAHYCAAAAAAAAgwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgIAAAAAAAB2AgAAAAAAAAAAAAAAAPC/" - ], - [ - 982, - 1, - "reindent", - null, - "AQAAAC4GAAAAAAAAMgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALgYAAAAAAAAuBgAAAAAAAAAAAAAAAPC/" - ], - [ - 983, - 1, - "paste", - null, - "AQAAADIGAAAAAAAAPwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" - ], - [ - 986, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAABzBwAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYgpmcm9tIHN0YWNvc3lzLm1vZGVsLmNvbW1lbnQgaW1wb3J0IENvbW1lbnQKCgpkZWYgZmluZF9jb21tZW50X2J5X2lkKGNvbW1lbnRfaWQpOgogICAgcmV0dXJuIGRiKCkuY29tbWVudChjb21tZW50X2lkKQoKCmRlZiBub3RpZnlfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGNvbW1lbnQubm90aWZpZWQgPSBkYXRldGltZS5ub3coKQogICAgZGIoKS5jb21tZW50LmJ1bGtfaW5zZXJ0KFthc2RpY3QoY29tbWVudCldKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBwdWJsaXNoX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBjb21tZW50LnB1Ymxpc2hlZCA9IGRhdGV0aW1lLm5vdygpCiAgICBkYigpLmNvbW1lbnQuYnVsa19pbnNlcnQoW2FzZGljdChjb21tZW50KV0pCiAgICBkYigpLmNvbW1pdCgpCgpkZWYgZGVsZXRlX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBkYigpKGRiKCkuY29tbWVudC5pZCA9PSBjb21tZW50LmlkKS5kZWxldGUoKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBmaW5kX25vdF9ub3RpZmllZF9jb21tZW50cygpOgogICAgcmV0dXJuIGRiKCkoZGIoKS5jb21tZW50Lm5vdGlmaWVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfbm90X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuIGRiKCkoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCA9PSBOb25lKS5zZWxlY3QoKQoKCmRlZiBmaW5kX3B1Ymxpc2hlZF9jb21tZW50c19ieV91cmwodXJsKToKICAgIHJldHVybiAoCiAgICAgICAgZGIoKSgoZGIoKS5jb21tZW50LnVybCA9PSB1cmwpICYgKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkpCiAgICAgICAgLnNlbGVjdCgpCiAgICAgICAgLnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCkKICAgICkKCgpkZWYgY291bnRfcHVibGlzaGVkX2NvbW1lbnRzKHVybCk6CiAgICByZXR1cm4gKAogICAgICAgIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpKS5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgPT0gTm9uZSkuY291bnQoKQogICAgKQoKCmRlZiBjcmVhdGVfY29tbWVudCh1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlKToKICAgIHJvdyA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICBkYigpLmNvbW1pdCgpCiAgICByZXR1cm4gQ29tbWVudCgKICAgICAgICBpZD1yb3cuaWQsCiAgICAgICAgdXJsPXJvdy51cmwsCiAgICAgICAgYXV0aG9yX25hbWU9cm93LmF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9cm93LmF1dGhvcl9ncmF2YXRhciwKICAgICAgICBjb250ZW50PXJvdy5jb250ZW50LAogICAgICAgIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsCiAgICAgICAgbm90aWZpZWQ9cm93Lm5vdGlmaWVkLAogICAgICAgIHB1Ymxpc2hlZD1yb3cucHVibGlzaGVkLAogICAgKQoAAAAAAAAAAHQHAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPwYAAAAAAAA/BgAAAAAAAAAAAAAAAPC/" - ], - [ - 999, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAEUBAAAAAAAARgEAAAAAAAAAAAAARgEAAAAAAABKAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARQEAAAAAAABFAQAAAAAAAAAAAAAA0HRA" - ], - [ - 1001, - 1, - "", - null, - "AQAAAEoBAAAAAAAAgQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1005, - 1, - "insert", - { - "characters": "()" - }, - "AgAAAEwBAAAAAAAATQEAAAAAAAAAAAAATQEAAAAAAABOAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATAEAAAAAAABMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1008, - 1, - "insert", - { - "characters": ".comment" - }, - "CQAAAFEBAAAAAAAAUgEAAAAAAAAAAAAAUgEAAAAAAABSAQAAAAAAAAEAAABbUgEAAAAAAABTAQAAAAAAAAAAAABTAQAAAAAAAFQBAAAAAAAAAAAAAFQBAAAAAAAAVQEAAAAAAAAAAAAAVQEAAAAAAABWAQAAAAAAAAAAAABWAQAAAAAAAFcBAAAAAAAAAAAAAFcBAAAAAAAAWAEAAAAAAAAAAAAAWAEAAAAAAABZAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1011, - 1, - "insert", - { - "characters": "()" - }, - "AgAAAFEBAAAAAAAAUgEAAAAAAAAAAAAAUgEAAAAAAABTAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQEAAAAAAABRAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1016, - 1, - "right_delete", - null, - "AQAAAFsBAAAAAAAAWwEAAAAAAAAKAAAAdGFibGVuYW1lXQ", - "AQAAAAAAAAABAAAAWwEAAAAAAABlAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1019, - 1, - "insert", - { - "characters": "comment." - }, - "CAAAAGMBAAAAAAAAZAEAAAAAAAAAAAAAZAEAAAAAAABlAQAAAAAAAAAAAABlAQAAAAAAAGYBAAAAAAAAAAAAAGYBAAAAAAAAZwEAAAAAAAAAAAAAZwEAAAAAAABoAQAAAAAAAAAAAABoAQAAAAAAAGkBAAAAAAAAAAAAAGkBAAAAAAAAagEAAAAAAAAAAAAAagEAAAAAAABrAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwEAAAAAAABjAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1024, - 1, - "right_delete", - null, - "AQAAAHYBAAAAAAAAdgEAAAAAAAATAAAAKip7ZmllbGRuYW1lOnZhbHVlfQ", - "AQAAAAAAAAABAAAAdgEAAAAAAACJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1025, - 1, - "insert", - { - "characters": "notii" - }, - "BQAAAHYBAAAAAAAAdwEAAAAAAAAAAAAAdwEAAAAAAAB4AQAAAAAAAAAAAAB4AQAAAAAAAHkBAAAAAAAAAAAAAHkBAAAAAAAAegEAAAAAAAAAAAAAegEAAAAAAAB7AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgEAAAAAAAB2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 1026, - 1, - "left_delete", - null, - "AQAAAHoBAAAAAAAAegEAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAAewEAAAAAAAB7AQAAAAAAAAAAAAAAAPC/" - ], - [ - 1027, - 1, - "insert", - { - "characters": "fied=" - }, - "BQAAAHoBAAAAAAAAewEAAAAAAAAAAAAAewEAAAAAAAB8AQAAAAAAAAAAAAB8AQAAAAAAAH0BAAAAAAAAAAAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAegEAAAAAAAB6AQAAAAAAAAAAAAAAAPC/" - ], - [ - 1030, - 1, - "cut", - null, - "AQAAADcBAAAAAAAANwEAAAAAAAAOAAAAZGF0ZXRpbWUubm93KCk", - "AQAAAAAAAAABAAAARQEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 1033, - 1, - "paste", - null, - "AQAAAHEBAAAAAAAAfwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcQEAAAAAAABxAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1036, - 1, - "right_delete", - null, - "AQAAAIUBAAAAAAAAhQEAAAAAAAArAAAAZGIoKS5jb21tZW50LmJ1bGtfaW5zZXJ0KFthc2RpY3QoY29tbWVudCldKQ", - "AQAAAAAAAAABAAAAsAEAAAAAAACFAQAAAAAAAAAAAAAAAEJA" - ], - [ - 1038, - 1, - "left_delete", - null, - "AQAAAIABAAAAAAAAgAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAgQEAAAAAAACBAQAAAAAAAAAAAAAAAAAA" - ], - [ - 1041, - 1, - "right_delete", - null, - "AQAAACQBAAAAAAAAJAEAAAAAAAATAAAAY29tbWVudC5ub3RpZmllZCA9IA", - "AQAAAAAAAAABAAAANwEAAAAAAAAkAQAAAAAAAAAAAAAAAEJA" - ], - [ - 1043, - 1, - "left_delete", - null, - "AQAAAB8BAAAAAAAAHwEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAAAA" - ], - [ - 1045, + 364, 1, "trim_trailing_white_space", null, - "AgAAAGwBAAAAAAAAbAEAAAAAAAAEAAAAICAgIB8BAAAAAAAAHwEAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAHwEAAAAAAAAfAQAAAAAAAAAAAAAAAPC/" + "AQAAAHECAAAAAAAAcQIAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAdQIAAAAAAAB1AgAAAAAAAAAAAAAAAPC/" ], [ - 1053, + 378, 1, "insert", { - "characters": "\n" + "characters": "equals_" }, - "AgAAAKMBAAAAAAAApAEAAAAAAAAAAAAApAEAAAAAAACoAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAowEAAAAAAACjAQAAAAAAAP///////+9/" + "BwAAAG8DAAAAAAAAcAMAAAAAAAAAAAAAcAMAAAAAAABxAwAAAAAAAAAAAABxAwAAAAAAAHIDAAAAAAAAAAAAAHIDAAAAAAAAcwMAAAAAAAAAAAAAcwMAAAAAAAB0AwAAAAAAAAAAAAB0AwAAAAAAAHUDAAAAAAAAAAAAAHUDAAAAAAAAdgMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAbwMAAAAAAABvAwAAAAAAAAAAAAAAAPC/" ], [ - 1054, - 1, - "paste", - null, - "AQAAAKgBAAAAAAAA7AEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqAEAAAAAAACoAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1065, - 1, - "paste", - null, - "AgAAANQBAAAAAAAA3QEAAAAAAAAAAAAA3QEAAAAAAADdAQAAAAAAAAgAAABub3RpZmllZA", - "AQAAAAAAAAABAAAA1AEAAAAAAADcAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1068, - 2, - "left_delete", - null, - "AgAAAO4BAAAAAAAA7gEAAAAAAABWAAAAICAgIGNvbW1lbnQucHVibGlzaGVkID0gZGF0ZXRpbWUubm93KCkKICAgIGRiKCkuY29tbWVudC5idWxrX2luc2VydChbYXNkaWN0KGNvbW1lbnQpXSntAQAAAAAAAO0BAAAAAAAAAQAAAAo", - "AQAAAAAAAAABAAAARAIAAAAAAADuAQAAAAAAAAAAAAAAAPC/" - ], - [ - 1074, - 1, - "insert", - { - "characters": "_" - }, - "AQAAAD4CAAAAAAAAPwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPgIAAAAAAAA+AgAAAAAAAAAAAAAAAPC/" - ], - [ - 1079, + 379, 1, "left_delete", null, - "AQAAAD4CAAAAAAAAPgIAAAAAAAABAAAAXw", - "AQAAAAAAAAABAAAAPwIAAAAAAAA/AgAAAAAAAAAAAAAA4GlA" + "AQAAAHUDAAAAAAAAdQMAAAAAAAABAAAAXw", + "AQAAAAAAAAABAAAAdgMAAAAAAAB2AwAAAAAAAAAAAAAAAPC/" ], [ - 1083, - 1, - "right_delete", - null, - "AQAAALoBAAAAAAAAugEAAAAAAAABAAAAXw", - "AQAAAAAAAAABAAAAugEAAAAAAAC6AQAAAAAAAAAAAAAAAPC/" - ], - [ - 1085, - 1, - "right_delete", - null, - "AQAAADYBAAAAAAAANgEAAAAAAAABAAAAXw", - "AQAAAAAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAwGhA" - ], - [ - 1092, - 1, - "right_delete", - null, - "AQAAACoAAAAAAAAAKgAAAAAAAAAeAAAAZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgYXNkaWN0", - "AQAAAAAAAAABAAAASAAAAAAAAAAqAAAAAAAAAAAAAAAAAAAA" - ], - [ - 1093, - 1, - "left_delete", - null, - "AQAAACkAAAAAAAAAKQAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 1108, - 1, - "toggle_breakpoint", - null, - "AQAAAO0DAAAAAAAAJwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA9AMAAAAAAAD0AwAAAAAAAAAAAAAAAPC/" - ], - [ - 1120, - 1, - "toggle_breakpoint", - null, - "AQAAAO0DAAAAAAAA7QMAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IGYyYzA0YTBkIC8vCg", - "AQAAAAAAAAABAAAAJgQAAAAAAAAmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1127, + 380, 1, "insert", { - "characters": "!" + "characters": "_comment(" }, - "AgAAADsEAAAAAAAAPAQAAAAAAAAAAAAAPAQAAAAAAAA8BAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAOwQAAAAAAAA8BAAAAAAAAAAAAAAAAPC/" + "CQAAAHUDAAAAAAAAdgMAAAAAAAAAAAAAdgMAAAAAAAB3AwAAAAAAAAAAAAB3AwAAAAAAAHgDAAAAAAAAAAAAAHgDAAAAAAAAeQMAAAAAAAAAAAAAeQMAAAAAAAB6AwAAAAAAAAAAAAB6AwAAAAAAAHsDAAAAAAAAAAAAAHsDAAAAAAAAfAMAAAAAAAAAAAAAfAMAAAAAAAB9AwAAAAAAAAAAAAB9AwAAAAAAAH4DAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAdQMAAAAAAAB1AwAAAAAAAAAAAAAAAPC/" ], [ - 1131, + 381, 1, "insert", { - "characters": "!" + "characters": "c1," }, - "AgAAAIUEAAAAAAAAhgQAAAAAAAAAAAAAhgQAAAAAAACGBAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAhQQAAAAAAACGBAAAAAAAAAAAAAAAAPC/" + "AwAAAH4DAAAAAAAAfwMAAAAAAAAAAAAAfwMAAAAAAACAAwAAAAAAAAAAAACAAwAAAAAAAIEDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAfgMAAAAAAAB+AwAAAAAAAAAAAAAAAPC/" ], [ - 1136, - 1, - "insert", - { - "characters": "def" - }, - "AwAAAJ0EAAAAAAAAngQAAAAAAAAAAAAAngQAAAAAAACfBAAAAAAAAAAAAACfBAAAAAAAAKAEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAnQQAAAAAAACdBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1137, + 382, 1, "insert", { "characters": " " }, - "AQAAAKAEAAAAAAAAoQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoAQAAAAAAACgBAAAAAAAAAAAAAAAAPC/" + "AQAAAIEDAAAAAAAAggMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" ], [ - 1138, + 385, + 1, + "insert", + { + "characters": ")" + }, + "AgAAAIkDAAAAAAAAigMAAAAAAAAAAAAAigMAAAAAAACKAwAAAAAAAAwAAAAuaWQgPT0gYzEuaWQ", + "AQAAAAAAAAABAAAAiQMAAAAAAACVAwAAAAAAAAAAAAAAAPC/" + ], + [ + 414, + 1, + "insert", + { + "characters": "\n\t" + }, + "AwAAAEgBAAAAAAAASQEAAAAAAAAAAAAASQEAAAAAAABNAQAAAAAAAAAAAABNAQAAAAAAAFEBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" + ], + [ + 417, + 1, + "right_delete", + null, + "AQAAAEkBAAAAAAAASQEAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAUQEAAAAAAABJAQAAAAAAAAAAAAAAAAAA" + ], + [ + 418, + 1, + "left_delete", + null, + "AQAAAEgBAAAAAAAASAEAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" + ], + [ + 420, + 1, + "insert", + { + "characters": "\\" + }, + "AQAAAEgBAAAAAAAASQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" + ], + [ + 421, 1, "insert", { "characters": "\n" }, - "AQAAAKEEAAAAAAAAogQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" + "AgAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" ], [ - 1141, - 1, - "insert", - { - "characters": " " - }, - "AQAAAKAEAAAAAAAAoQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoAQAAAAAAACgBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1142, - 1, - "insert", - { - "characters": "\n\n" - }, - "AgAAAKEEAAAAAAAAogQAAAAAAAAAAAAAogQAAAAAAACjBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1143, - 1, - "paste", - null, - "AQAAAKMEAAAAAAAAJgUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAowQAAAAAAACjBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1147, - 1, - "insert", - { - "characters": "find_" - }, - "BQAAAKEEAAAAAAAAogQAAAAAAAAAAAAAogQAAAAAAACjBAAAAAAAAAAAAACjBAAAAAAAAKQEAAAAAAAAAAAAAKQEAAAAAAAApQQAAAAAAAAAAAAApQQAAAAAAACmBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAoQQAAAAAAAChBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1148, - 1, - "insert", - { - "characters": "lastè" - }, - "BQAAAKYEAAAAAAAApwQAAAAAAAAAAAAApwQAAAAAAACoBAAAAAAAAAAAAACoBAAAAAAAAKkEAAAAAAAAAAAAAKkEAAAAAAAAqgQAAAAAAAAAAAAAqgQAAAAAAACrBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApgQAAAAAAACmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1149, - 1, - "left_delete", - null, - "AQAAAKoEAAAAAAAAqgQAAAAAAAACAAAAw6g", - "AQAAAAAAAAABAAAAqwQAAAAAAACrBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1150, - 1, - "insert", - { - "characters": "_" - }, - "AQAAAKoEAAAAAAAAqwQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqgQAAAAAAACqBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1151, - 3, - "left_delete", - null, - "AwAAAKoEAAAAAAAAqgQAAAAAAAABAAAAX6kEAAAAAAAAqQQAAAAAAAABAAAAdKgEAAAAAAAAqAQAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAAqwQAAAAAAACrBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1152, - 1, - "insert", - { - "characters": "test_publ" - }, - "CQAAAKgEAAAAAAAAqQQAAAAAAAAAAAAAqQQAAAAAAACqBAAAAAAAAAAAAACqBAAAAAAAAKsEAAAAAAAAAAAAAKsEAAAAAAAArAQAAAAAAAAAAAAArAQAAAAAAACtBAAAAAAAAAAAAACtBAAAAAAAAK4EAAAAAAAAAAAAAK4EAAAAAAAArwQAAAAAAAAAAAAArwQAAAAAAACwBAAAAAAAAAAAAACwBAAAAAAAALEEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqAQAAAAAAACoBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1153, - 1, - "insert", - { - "characters": "ished_co" - }, - "CAAAALEEAAAAAAAAsgQAAAAAAAAAAAAAsgQAAAAAAACzBAAAAAAAAAAAAACzBAAAAAAAALQEAAAAAAAAAAAAALQEAAAAAAAAtQQAAAAAAAAAAAAAtQQAAAAAAAC2BAAAAAAAAAAAAAC2BAAAAAAAALcEAAAAAAAAAAAAALcEAAAAAAAAuAQAAAAAAAAAAAAAuAQAAAAAAAC5BAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAsQQAAAAAAACxBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1159, - 1, - "insert", - { - "characters": "recent" - }, - "BwAAAKYEAAAAAAAApwQAAAAAAAAAAAAApwQAAAAAAACnBAAAAAAAAAYAAABsYXRlc3SnBAAAAAAAAKgEAAAAAAAAAAAAAKgEAAAAAAAAqQQAAAAAAAAAAAAAqQQAAAAAAACqBAAAAAAAAAAAAACqBAAAAAAAAKsEAAAAAAAAAAAAAKsEAAAAAAAArAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAArAQAAAAAAACmBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1162, - 1, - "insert", - { - "characters": "mments" - }, - "BgAAALkEAAAAAAAAugQAAAAAAAAAAAAAugQAAAAAAAC7BAAAAAAAAAAAAAC7BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuQQAAAAAAAC5BAAAAAAAAAAAAAAAAPC/" - ], - [ - 1163, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAL8EAAAAAAAAwQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvwQAAAAAAAC/BAAAAAAAAAAAAAAAAPC/" - ], - [ - 1165, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAMEEAAAAAAAAwgQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQQAAAAAAADBBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1177, - 1, - "paste", - null, - "AQAAAMMEAAAAAAAAXAUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwwQAAAAAAADDBAAAAAAAAAAAAAAAAPC/" - ], - [ - 1183, - 1, - "right_delete", - null, - "AQAAAN0EAAAAAAAA3QQAAAAAAAAdAAAAKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmICg", - "AQAAAAAAAAABAAAA3QQAAAAAAAD6BAAAAAAAAAAAAAAAAPC/" - ], - [ - 1188, - 1, - "right_delete", - null, - "AQAAAPsEAAAAAAAA+wQAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAA+wQAAAAAAAD7BAAAAAAAAAAAAAAAAPC/" - ], - [ - 1195, - 1, - "", - null, - "AQAAAA0FAAAAAAAAGwUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADQUAAAAAAAANBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1198, - 1, - "insert", - { - "characters": "10" - }, - "AwAAABkFAAAAAAAAGgUAAAAAAAAAAAAAGgUAAAAAAAAaBQAAAAAAAAEAAAAyGgUAAAAAAAAbBQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGgUAAAAAAAAZBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1203, - 1, - "", - null, - "AQAAAJcDAAAAAAAAnwMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlwMAAAAAAACXAwAAAAAAAAAAAAAAAPC/" - ], - [ - 1210, - 1, - "paste", - null, - "AQAAAJ8DAAAAAAAArAMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAnwMAAAAAAACfAwAAAAAAAAAAAAAAAPC/" - ], - [ - 1211, - 1, - "insert", - { - "characters": "published" - }, - "CQAAAKwDAAAAAAAArQMAAAAAAAAAAAAArQMAAAAAAACuAwAAAAAAAAAAAACuAwAAAAAAAK8DAAAAAAAAAAAAAK8DAAAAAAAAsAMAAAAAAAAAAAAAsAMAAAAAAACxAwAAAAAAAAAAAACxAwAAAAAAALIDAAAAAAAAAAAAALIDAAAAAAAAswMAAAAAAAAAAAAAswMAAAAAAAC0AwAAAAAAAAAAAAC0AwAAAAAAALUDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAArAMAAAAAAACsAwAAAAAAAAAAAAAAAPC/" - ], - [ - 1215, - 1, - "right_delete", - null, - "AQAAAL8DAAAAAAAAvwMAAAAAAAAgAAAALnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCk", - "AQAAAAAAAAABAAAAvwMAAAAAAADfAwAAAAAAAP///////+9/" - ], - [ - 1217, - 1, - "left_delete", - null, - "AQAAALYDAAAAAAAAtgMAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAtwMAAAAAAAC3AwAAAAAAAAAAAAAAAAAA" - ], - [ - 1219, - 1, - "trim_trailing_white_space", - null, - "AgAAAM4FAAAAAAAAzgUAAAAAAAABAAAAILYDAAAAAAAAtgMAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAtgMAAAAAAAC2AwAAAAAAAAAAAAAAAPC/" - ], - [ - 1225, - 1, - "paste", - null, - "AQAAAAIFAAAAAAAAIAUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAgUAAAAAAAACBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1226, - 1, - "insert", - { - "characters": "," - }, - "AQAAACAFAAAAAAAAIQUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIAUAAAAAAAAgBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1227, - 1, - "insert", - { - "characters": " " - }, - "AQAAACEFAAAAAAAAIgUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIQUAAAAAAAAhBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1230, - 1, - "insert", - { - "characters": "~" - }, - "AQAAAAoFAAAAAAAACwUAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACgUAAAAAAAAKBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1234, - 1, - "right_delete", - null, - "AQAAADwFAAAAAAAAPAUAAAAAAAAgAAAALnNvcnQobGFtYmRhIHJvdzogcm93LnB1Ymxpc2hlZCk", - "AQAAAAAAAAABAAAAPAUAAAAAAABcBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1236, - 1, - "left_delete", - null, - "AQAAADMFAAAAAAAAMwUAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAANAUAAAAAAAA0BQAAAAAAAAAAAAAAAAAA" - ], - [ - 1241, - 1, - "left_delete", - null, - "AQAAAEIFAAAAAAAAQgUAAAAAAACEAAAACiAgICAgICAgICAgIENvbW1lbnQuc2VsZWN0KCkKICAgICAgICAgICAgLndoZXJlKENvbW1lbnQucHVibGlzaGVkKQogICAgICAgICAgICAub3JkZXJfYnkoLUNvbW1lbnQucHVibGlzaGVkKQogICAgICAgICAgICAubGltaXQoMTAp", - "AQAAAAAAAAABAAAAxgUAAAAAAABCBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1243, - 1, - "left_delete", - null, - "AQAAAEIFAAAAAAAAQgUAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAQwUAAAAAAABDBQAAAAAAAAAAAAAAAAAA" - ], - [ - 1245, - 1, - "trim_trailing_white_space", - null, - "AQAAADMFAAAAAAAAMwUAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAQgUAAAAAAABCBQAAAAAAAAAAAAAAAPC/" - ], - [ - 1251, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADWBwAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKCmZyb20gc3RhY29zeXMuZGIgaW1wb3J0IGRiCmZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKCmRlZiBmaW5kX2NvbW1lbnRfYnlfaWQoY29tbWVudF9pZCk6CiAgICByZXR1cm4gZGIoKS5jb21tZW50KGNvbW1lbnRfaWQpCgoKZGVmIG5vdGlmeV9jb21tZW50KGNvbW1lbnQ6IENvbW1lbnQpOgogICAgZGIoKShkYigpLmNvbW1lbnQuaWQgPT0gY29tbWVudC5pZCkudXBkYXRlKG5vdGlmaWVkPWRhdGV0aW1lLm5vdygpKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBwdWJsaXNoX2NvbW1lbnQoY29tbWVudDogQ29tbWVudCk6CiAgICBkYigpKGRiKCkuY29tbWVudC5pZCA9PSBjb21tZW50LmlkKS51cGRhdGUocHVibGlzaGVkPWRhdGV0aW1lLm5vdygpKQogICAgZGIoKS5jb21taXQoKQoKCmRlZiBkZWxldGVfY29tbWVudChjb21tZW50OiBDb21tZW50KToKICAgIGRiKCkoZGIoKS5jb21tZW50LmlkID09IGNvbW1lbnQuaWQpLmRlbGV0ZSgpCiAgICBkYigpLmNvbW1pdCgpCgoKZGVmIGZpbmRfbm90X25vdGlmaWVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQubm90aWZpZWQgPT0gTm9uZSkuc2VsZWN0KCkKCgpkZWYgZmluZF9ub3RfcHVibGlzaGVkX2NvbW1lbnRzKCk6CiAgICByZXR1cm4gZGIoKShkYigpLmNvbW1lbnQucHVibGlzaGVkID09IE5vbmUpLnNlbGVjdCgpCgoKZGVmIGZpbmRfcHVibGlzaGVkX2NvbW1lbnRzX2J5X3VybCh1cmwpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKChkYigpLmNvbW1lbnQudXJsID09IHVybCkgJiAoZGIoKS5jb21tZW50LnB1Ymxpc2hlZCAhPSBOb25lKSkKICAgICAgICAuc2VsZWN0KG9yZGVyYnk9ZGIoKS5jb21tZW50LnB1Ymxpc2hlZCkKICAgICkKCgpkZWYgY291bnRfcHVibGlzaGVkX2NvbW1lbnRzKHVybCk6CiAgICByZXR1cm4gKAogICAgICAgIGRiKCkoKGRiKCkuY29tbWVudC51cmwgPT0gdXJsKSAmIChkYigpLmNvbW1lbnQucHVibGlzaGVkICE9IE5vbmUpKS5jb3VudCgpCiAgICAgICAgaWYgdXJsCiAgICAgICAgZWxzZSBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkuY291bnQoKQogICAgKQoKZGVmIGZpbmRfcmVjZW50X3B1Ymxpc2hlZF9jb21tZW50cygpOgogICAgcmV0dXJuICgKICAgICAgICBkYigpKGRiKCkuY29tbWVudC5wdWJsaXNoZWQgIT0gTm9uZSkKICAgICAgICAuc2VsZWN0KG9yZGVyYnk9fmRiKCkuY29tbWVudC5wdWJsaXNoZWQsIGxpbWl0Ynk9KDAsIDEwKSkKICAgICkKCmRlZiBjcmVhdGVfY29tbWVudCh1cmwsIGF1dGhvcl9uYW1lLCBhdXRob3Jfc2l0ZSwgYXV0aG9yX2dyYXZhdGFyLCBtZXNzYWdlKToKICAgIHJvdyA9IGRiKCkuY29tbWVudC5pbnNlcnQoCiAgICAgICAgdXJsPXVybCwKICAgICAgICBhdXRob3JfbmFtZT1hdXRob3JfbmFtZSwKICAgICAgICBhdXRob3Jfc2l0ZT1hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9YXV0aG9yX2dyYXZhdGFyLAogICAgICAgIGNvbnRlbnQ9bWVzc2FnZSwKICAgICAgICBjcmVhdGVkPWRhdGV0aW1lLm5vdygpLAogICAgICAgIG5vdGlmaWVkPU5vbmUsCiAgICAgICAgcHVibGlzaGVkPU5vbmUsCiAgICApCiAgICBkYigpLmNvbW1pdCgpCiAgICByZXR1cm4gQ29tbWVudCgKICAgICAgICBpZD1yb3cuaWQsCiAgICAgICAgdXJsPXJvdy51cmwsCiAgICAgICAgYXV0aG9yX25hbWU9cm93LmF1dGhvcl9uYW1lLAogICAgICAgIGF1dGhvcl9zaXRlPXJvdy5hdXRob3Jfc2l0ZSwKICAgICAgICBhdXRob3JfZ3JhdmF0YXI9cm93LmF1dGhvcl9ncmF2YXRhciwKICAgICAgICBjb250ZW50PXJvdy5jb250ZW50LAogICAgICAgIGNyZWF0ZWQ9cm93LmNyZWF0ZWQsCiAgICAgICAgbm90aWZpZWQ9cm93Lm5vdGlmaWVkLAogICAgICAgIHB1Ymxpc2hlZD1yb3cucHVibGlzaGVkLAogICAgKQoAAAAAAAAAAMIHAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlgQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" - ], - [ - 1254, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAACkAAAAAAAAAKgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKQAAAAAAAAApAAAAAAAAAAAAAAAAAPC/" - ], - [ - 1255, - 1, - "paste", - null, - "AQAAACoAAAAAAAAATwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 1256, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAE8AAAAAAAAAUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" - ], - [ - 1260, - 1, - "", - null, - "AgAAADwAAAAAAAAAUAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAABMAAABjb25zaWRlci11c2luZy13aXRo", - "AQAAAAAAAAABAAAAPAAAAAAAAABPAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "stacosys/service/rssfeed.py", - "settings": - { - "buffer_size": 1754, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 15, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAAbAAAAAAAAABtAAAAAAAAAAAAAABtAAAAAAAAAG4AAAAAAAAAAAAAAG4AAAAAAAAAbwAAAAAAAAAAAAAAbwAAAAAAAABwAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 16, - 1, - "insert", - { - "characters": " stacs" - }, - "BgAAAHAAAAAAAAAAcQAAAAAAAAAAAAAAcQAAAAAAAAByAAAAAAAAAAAAAAByAAAAAAAAAHMAAAAAAAAAAAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB1AAAAAAAAAAAAAAB1AAAAAAAAAHYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcAAAAAAAAABwAAAAAAAAAAAAAAAAAPC/" - ], - [ - 17, - 1, - "left_delete", - null, - "AQAAAHUAAAAAAAAAdQAAAAAAAAABAAAAcw", - "AQAAAAAAAAABAAAAdgAAAAAAAAB2AAAAAAAAAAAAAAAAAPC/" - ], - [ - 18, - 1, - "insert", - { - "characters": "osys.db" - }, - "BwAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAB3AAAAAAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAB6AAAAAAAAAAAAAAB6AAAAAAAAAHsAAAAAAAAAAAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdQAAAAAAAAB1AAAAAAAAAAAAAAAAAPC/" - ], - [ - 19, - 1, - "insert", - { - "characters": "." - }, - "AQAAAHwAAAAAAAAAfQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" - ], - [ - 20, - 1, - "left_delete", - null, - "AQAAAHwAAAAAAAAAfAAAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAfQAAAAAAAAB9AAAAAAAAAAAAAAAAAPC/" - ], - [ - 21, - 1, - "insert", - { - "characters": " import" - }, - "BwAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAAAAPC/" - ], - [ - 22, - 1, - "insert", - { - "characters": " dai" - }, - "BAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" - ], - [ - 23, - 1, - "left_delete", - null, - "AQAAAIYAAAAAAAAAhgAAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 24, - 1, - "insert", - { - "characters": "o" - }, - "AQAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "paste", - null, - "AQAAAOQCAAAAAAAAAgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5AIAAAAAAADkAgAAAAAAAAAAAAAAAPC/" - ], - [ - 39, - 1, - "insert", - { - "characters": "dao/" - }, - "BAAAAOQCAAAAAAAA5QIAAAAAAAAAAAAA5QIAAAAAAADmAgAAAAAAAAAAAADmAgAAAAAAAOcCAAAAAAAAAAAAAOcCAAAAAAAA6AIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5AIAAAAAAADkAgAAAAAAAAAAAAAAAPC/" - ], - [ - 40, - 1, - "left_delete", - null, - "AQAAAOcCAAAAAAAA5wIAAAAAAAABAAAALw", - "AQAAAAAAAAABAAAA6AIAAAAAAADoAgAAAAAAAAAAAAAAAPC/" - ], - [ - 41, - 1, - "insert", - { - "characters": "." - }, - "AQAAAOcCAAAAAAAA6AIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5wIAAAAAAADnAgAAAAAAAAAAAAAAAPC/" - ], - [ - 44, - 1, - "insert", - { - "characters": "):" - }, - "AgAAAAcDAAAAAAAACAMAAAAAAAAAAAAACAMAAAAAAAAJAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABwMAAAAAAAAHAwAAAAAAAAAAAAAAAPC/" - ], - [ - 49, - 1, - "right_delete", - null, - "AQAAAAoDAAAAAAAACgMAAAAAAACNAAAAICAgICAgICAgICAgQ29tbWVudC5zZWxlY3QoKQogICAgICAgICAgICAud2hlcmUoQ29tbWVudC5wdWJsaXNoZWQpCiAgICAgICAgICAgIC5vcmRlcl9ieSgtQ29tbWVudC5wdWJsaXNoZWQpCiAgICAgICAgICAgIC5saW1pdCgxMCkKICAgICAgICAp", - "AQAAAAAAAAABAAAACgMAAAAAAACXAwAAAAAAAAAAAAAAAPC/" - ], - [ - 50, - 2, - "left_delete", - null, - "AgAAAAkDAAAAAAAACQMAAAAAAAABAAAACggDAAAAAAAACAMAAAAAAAABAAAAOg", - "AQAAAAAAAAABAAAACgMAAAAAAAAKAwAAAAAAAAAAAAAAAPC/" - ], - [ - 58, - 1, - "right_delete", - null, - "AQAAAIgAAAAAAAAAiAAAAAAAAAAqAAAAZnJvbSBzdGFjb3N5cy5tb2RlbC5jb21tZW50IGltcG9ydCBDb21tZW50", - "AQAAAAAAAAABAAAAsgAAAAAAAACIAAAAAAAAAAAAAAAAAAAA" - ], - [ - 59, - 1, - "left_delete", - null, - "AQAAAIcAAAAAAAAAhwAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "stacosys/model/comment.py", - "settings": - { - "buffer_size": 421, - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 12, - 1, - "insert", - { - "characters": "#" - }, - "AQAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwAAAAAAAABjAAAAAAAAAAAAAAAAAAAA" - ], - [ - 17, - 1, - "left_delete", - null, - "AQAAAJ8AAAAAAAAAnwAAAAAAAAAJAAAAQmFzZU1vZGVs", - "AQAAAAAAAAABAAAAnwAAAAAAAACoAAAAAAAAAAAAAAAAAPC/" - ], - [ - 23, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" - ], - [ - 24, - 1, - "paste", - null, - "AQAAANABAAAAAAAAoQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0AEAAAAAAADQAQAAAAAAAAAAAAAAAPC/" - ], - [ - 31, - 1, - "paste", - null, - "AQAAAJAAAAAAAAAAmgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAAAA" - ], - [ - 36, - 1, - "paste", - null, - "AgAAACsAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABqAAAAAAAAAGQAAABmcm9tIHBlZXdlZSBpbXBvcnQgQ2hhckZpZWxkLCBEYXRlVGltZUZpZWxkLCBUZXh0RmllbGQKCiNmcm9tIHN0YWNvc3lzLmRiLmRhdGFiYXNlIGltcG9ydCBCYXNlTW9kZWwK", - "AQAAAAAAAAABAAAAKwAAAAAAAACPAAAAAAAAAAAAAAAAAAAA" - ], - [ - 37, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAGoAAAAAAAAAawAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAagAAAAAAAABqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 41, - 1, - "right_delete", - null, - "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAKQ", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 43, - 1, - "right_delete", - null, - "AQAAAIQAAAAAAAAAhAAAAAAAAAABAAAAKA", - "AQAAAAAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" - ], - [ - 47, - 1, - "insert", - { - "characters": "str" - }, - "BAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACRAAAAAAAAAAsAAABDaGFyRmllbGQoKZEAAAAAAAAAkgAAAAAAAAAAAAAAkgAAAAAAAACTAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAAAAAAAAACbAAAAAAAAAP///////+9/" - ], - [ - 54, - 1, - "insert", - { - "characters": "datetime" - }, - "CQAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACjAAAAAAAAAA8AAABEYXRlVGltZUZpZWxkKCmjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAApQAAAAAAAACmAAAAAAAAAAAAAACmAAAAAAAAAKcAAAAAAAAAAAAAAKcAAAAAAAAAqAAAAAAAAAAAAAAAqAAAAAAAAACpAAAAAAAAAAAAAACpAAAAAAAAAKoAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAogAAAAAAAACxAAAAAAAAAP///////+9/" - ], - [ - 62, - 1, - "paste", - null, - "AgAAALoAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAACYAAABEYXRlVGltZUZpZWxkKG51bGw9VHJ1ZSwgZGVmYXVsdD1Ob25lKQ", - "AQAAAAAAAAABAAAAugAAAAAAAADgAAAAAAAAAP///////+9/" - ], - [ - 66, - 1, - "paste", - null, - "AgAAANMAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADbAAAAAAAAACYAAABEYXRlVGltZUZpZWxkKG51bGw9VHJ1ZSwgZGVmYXVsdD1Ob25lKQ", - "AQAAAAAAAAABAAAA0wAAAAAAAAD5AAAAAAAAAP///////+9/" - ], - [ - 70, - 1, - "insert", - { - "characters": "str" - }, - "BAAAAO4AAAAAAAAA7wAAAAAAAAAAAAAA7wAAAAAAAADvAAAAAAAAAAsAAABDaGFyRmllbGQoKe8AAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAADxAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7gAAAAAAAAD5AAAAAAAAAP///////+9/" - ], - [ - 74, - 1, - "insert", - { - "characters": ":" - }, - "AgAAAKAAAAAAAAAAoQAAAAAAAAAAAAAAoQAAAAAAAAChAAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAoAAAAAAAAAChAAAAAAAAAAAAAAAAAPC/" - ], - [ - 78, - 1, - "insert", - { - "characters": ":" - }, - "AgAAAI4AAAAAAAAAjwAAAAAAAAAAAAAAjwAAAAAAAACPAAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAjwAAAAAAAACOAAAAAAAAAAAAAAAAAPC/" - ], - [ - 82, - 1, - "insert", - { - "characters": ":" - }, - "AgAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC5AAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAuAAAAAAAAAC5AAAAAAAAAAAAAAAAAPC/" - ], - [ - 85, - 1, - "insert", - { - "characters": ":" - }, - "AgAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADSAAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAA0QAAAAAAAADSAAAAAAAAAAAAAAAAAPC/" - ], - [ - 89, - 1, - "insert", - { - "characters": ":" - }, - "AgAAAOwAAAAAAAAA7QAAAAAAAAAAAAAA7QAAAAAAAADtAAAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAA7AAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 92, - 1, - "insert", - { - "characters": ":" - }, - "AgAAAAIBAAAAAAAAAwEAAAAAAAAAAAAAAwEAAAAAAAADAQAAAAAAAAEAAAA9", - "AQAAAAAAAAABAAAAAwEAAAAAAAACAQAAAAAAAAAAAAAAAPC/" - ], - [ - 95, - 1, - "insert", - { - "characters": "str" - }, - "BAAAAAQBAAAAAAAABQEAAAAAAAAAAAAABQEAAAAAAAAFAQAAAAAAABUAAABDaGFyRmllbGQoZGVmYXVsdD0iIikFAQAAAAAAAAYBAAAAAAAAAAAAAAYBAAAAAAAABwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABAEAAAAAAAAZAQAAAAAAAP///////+9/" - ], - [ - 99, - 1, - "insert", - { - "characters": ":" - }, - "AgAAABwBAAAAAAAAHQEAAAAAAAAAAAAAHQEAAAAAAAAdAQAAAAAAABcAAAA9IENoYXJGaWVsZChkZWZhdWx0PSIiKQ", - "AQAAAAAAAAABAAAAHAEAAAAAAAAzAQAAAAAAAP///////+9/" - ], - [ - 100, - 1, - "insert", - { - "characters": " str" - }, - "BAAAAB0BAAAAAAAAHgEAAAAAAAAAAAAAHgEAAAAAAAAfAQAAAAAAAAAAAAAfAQAAAAAAACABAAAAAAAAAAAAACABAAAAAAAAIQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHQEAAAAAAAAdAQAAAAAAAAAAAAAAAPC/" - ], - [ - 105, - 1, - "insert", - { - "characters": ":" - }, - "AgAAAC4BAAAAAAAALwEAAAAAAAAAAAAALwEAAAAAAAAvAQAAAAAAAA0AAAA9IFRleHRGaWVsZCgp", - "AQAAAAAAAAABAAAALgEAAAAAAAA7AQAAAAAAAP///////+9/" - ], - [ - 106, - 1, - "insert", - { - "characters": " str" - }, - "BAAAAC8BAAAAAAAAMAEAAAAAAAAAAAAAMAEAAAAAAAAxAQAAAAAAAAAAAAAxAQAAAAAAADIBAAAAAAAAAAAAADIBAAAAAAAAMwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALwEAAAAAAAAvAQAAAAAAAAAAAAAAAPC/" - ], - [ - 112, - 1, - "right_delete", - null, - "AQAAADUBAAAAAAAANQEAAAAAAADRAAAAQGRhdGFjbGFzcwpjbGFzcyBFbWFpbDoKICAgIGlkOiBpbnQKICAgIGVuY29kaW5nOiBzdHIKICAgIGRhdGU6IGRhdGV0aW1lCiAgICBmcm9tX2FkZHI6IHN0cgogICAgdG9fYWRkcjogc3RyCiAgICBzdWJqZWN0OiBzdHIKICAgIHBhcnRzOiBMaXN0W1BhcnRdCiAgICBhdHRhY2htZW50czogTGlzdFtBdHRhY2htZW50XQogICAgcGxhaW5fdGV4dF9jb250ZW50OiBzdHI", - "AQAAAAAAAAABAAAANQEAAAAAAAAGAgAAAAAAAAAAAAAAAAAA" - ], - [ - 113, - 1, - "left_delete", - null, - "AQAAADQBAAAAAAAANAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" - ], - [ - 120, - 1, - "insert", - { - "characters": "," - }, - "AQAAAEwAAAAAAAAATQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATAAAAAAAAABMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 121, - 1, - "insert", - { - "characters": " asd" - }, - "BAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAABPAAAAAAAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAUQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATQAAAAAAAABNAAAAAAAAAAAAAAAAAPC/" - ], - [ - 122, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":3,\"sortText\":\"09.9999.asdict\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/model/comment.py\",\"position\":{\"line\":3,\"character\":38},\"symbolLabel\":\"asdict\",\"funcParensDisabled\":true,\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"asdict\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "asdict" - }, - "AgAAAE4AAAAAAAAATgAAAAAAAAADAAAAYXNkTgAAAAAAAABUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQAAAAAAAABRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 128, - 2, - "left_delete", - null, - "AgAAAE0AAAAAAAAATQAAAAAAAAAHAAAAIGFzZGljdEwAAAAAAAAATAAAAAAAAAABAAAALA", - "AQAAAAAAAAABAAAATQAAAAAAAABUAAAAAAAAAAAAAAAAAPC/" - ], - [ - 136, - 1, - "insert", - { - "characters": "\nid:" - }, - "BQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAjAAAAAAAAACNAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 137, - 1, - "insert", - { - "characters": " int" - }, - "BAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" - ], - [ - 138, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":7,\"sortText\":\"09.9999.int\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/model/comment.py\",\"position\":{\"line\":8,\"character\":11},\"symbolLabel\":\"int\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"int\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "int" - }, - "AgAAAI4AAAAAAAAAjgAAAAAAAAADAAAAaW50jgAAAAAAAACRAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 147, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAABAAQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmZyb20gZGF0YWNsYXNzZXMgaW1wb3J0IGRhdGFjbGFzcwpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQoKQGRhdGFjbGFzcwpjbGFzcyBDb21tZW50OgogICAgaWQ6IGludAogICAgdXJsIDogc3RyCiAgICBjcmVhdGVkIDogZGF0ZXRpbWUKICAgIG5vdGlmaWVkIDogZGF0ZXRpbWUKICAgIHB1Ymxpc2hlZCA6IGRhdGV0aW1lCiAgICBhdXRob3JfbmFtZSA6IHN0cgogICAgYXV0aG9yX3NpdGUgOiBzdHIKICAgIGF1dGhvcl9ncmF2YXRhciA6IHN0cgogICAgY29udGVudCA6IHN0cgoAAAAAAAAAADkBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQAEAAAAAAABAAQAAAAAAAAAAAAAAAPC/" - ], - [ - 151, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" - ], - [ - 152, - 1, - "insert", - { - "characters": " 0" - }, - "AgAAAJQAAAAAAAAAlQAAAAAAAAAAAAAAlQAAAAAAAACWAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlAAAAAAAAACUAAAAAAAAAAAAAAAAAPC/" - ], - [ - 155, - 1, - "cut", - null, - "AQAAAIsAAAAAAAAAiwAAAAAAAAALAAAAaWQ6IGludCA9IDA", - "AQAAAAAAAAABAAAAlgAAAAAAAACLAAAAAAAAAAAAAAAAAEJA" - ], - [ - 157, - 1, - "left_delete", - null, - "AQAAAIYAAAAAAAAAhgAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAAAA" - ], - [ - 160, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAADABAAAAAAAAMQEAAAAAAAAAAAAAMQEAAAAAAAA1AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMAEAAAAAAAAwAQAAAAAAAAAAAAAAAPC/" - ], - [ - 161, - 1, - "paste", - null, - "AQAAADUBAAAAAAAAQAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" - ], - [ - 163, - 1, - "cut", - null, - "AQAAADUBAAAAAAAANQEAAAAAAAALAAAAaWQ6IGludCA9IDA", - "AQAAAAAAAAABAAAAQAEAAAAAAAA1AQAAAAAAAAAAAAAAAEJA" - ], - [ - 165, - 1, - "left_delete", - null, - "AQAAADABAAAAAAAAMAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAMQEAAAAAAAAxAQAAAAAAAAAAAAAAAAAA" - ], - [ - 167, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAGJA" - ], - [ - 168, - 1, - "paste", - null, - "AQAAAIkAAAAAAAAAlAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAPC/" - ], - [ - 170, + 422, 1, "insert", { "characters": "\t" }, - "AQAAAIkAAAAAAAAAjQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAAAA" + "AQAAAE4BAAAAAAAAUgEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAATgEAAAAAAABOAQAAAAAAAAAAAAAAAPC/" ], [ - 173, + 424, + 1, + "right_delete", + null, + "AQAAAEoBAAAAAAAASgEAAAAAAAAIAAAAICAgICAgICA", + "AQAAAAAAAAABAAAAUgEAAAAAAABKAQAAAAAAAAAAAAAAAAAA" + ], + [ + 425, 2, "left_delete", null, - "AgAAAIcAAAAAAAAAhwAAAAAAAAABAAAAIIYAAAAAAAAAhgAAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" + "AgAAAEkBAAAAAAAASQEAAAAAAAABAAAACkgBAAAAAAAASAEAAAAAAAABAAAAXA", + "AQAAAAAAAAABAAAASgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" ], [ - 175, + 434, 1, "insert", { - "characters": " " + "characters": "\\" }, - "AgAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAgF9A" + "AQAAAEgBAAAAAAAASQEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" ], [ - 176, - 1, - "left_delete", - null, - "AQAAAKYAAAAAAAAApgAAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAPC/" - ], - [ - 177, + 435, 1, "insert", { - "characters": "=" + "characters": "\n\t" }, - "AQAAAKYAAAAAAAAApwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApgAAAAAAAACmAAAAAAAAAAAAAAAAAPC/" + "AwAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAABOAQAAAAAAAFIBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" ], [ - 178, + 438, 1, "insert", { - "characters": " " + "characters": "\\" }, - "AQAAAKcAAAAAAAAAqAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApwAAAAAAAACnAAAAAAAAAAAAAAAAAPC/" + "AQAAAIcBAAAAAAAAiAEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" ], [ - 179, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAKgAAAAAAAAAqgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqAAAAAAAAACoAAAAAAAAAAAAAAAAAPC/" - ], - [ - 183, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAMAAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAADCAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwAAAAAAAAADAAAAAAAAAAAAAAAAAAPC/" - ], - [ - 184, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAMIAAAAAAAAAwwAAAAAAAAAAAAAAwwAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMUAAAAAAAAAAAAAAMUAAAAAAAAAxgAAAAAAAAAAAAAAxgAAAAAAAADHAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwgAAAAAAAADCAAAAAAAAAAAAAAAAAPC/" - ], - [ - 186, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAN4AAAAAAAAA3wAAAAAAAAAAAAAA3wAAAAAAAADgAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3gAAAAAAAADeAAAAAAAAAAAAAAAAgG9A" - ], - [ - 187, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 189, - 1, - "trim_trailing_white_space", - null, - "AgAAAFEBAAAAAAAAUQEAAAAAAAAEAAAAICAgIJYAAAAAAAAAlgAAAAAAAAACAAAAICA", - "AQAAAAAAAAABAAAA5QAAAAAAAADlAAAAAAAAAAAAAAAAAPC/" - ], - [ - 201, - 1, - "insert", - { - "characters": "datetime." - }, - "CgAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAAAQAAABOb25lwgAAAAAAAADDAAAAAAAAAAAAAADDAAAAAAAAAMQAAAAAAAAAAAAAAMQAAAAAAAAAxQAAAAAAAAAAAAAAxQAAAAAAAADGAAAAAAAAAAAAAADGAAAAAAAAAMcAAAAAAAAAAAAAAMcAAAAAAAAAyAAAAAAAAAAAAAAAyAAAAAAAAADJAAAAAAAAAAAAAADJAAAAAAAAAMoAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQAAAAAAAADFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 202, - 1, - "insert", - { - "characters": "de" - }, - "AgAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADMAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 210, - 1, - "insert", - { - "characters": "ow" - }, - "AwAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADLAAAAAAAAAAIAAABkZcsAAAAAAAAAzAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAygAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 211, - 2, - "left_delete", - null, - "AgAAAMsAAAAAAAAAywAAAAAAAAABAAAAd8oAAAAAAAAAygAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 212, - 1, - "insert", - { - "characters": "now" - }, - "AwAAAMoAAAAAAAAAywAAAAAAAAAAAAAAywAAAAAAAADMAAAAAAAAAAAAAADMAAAAAAAAAM0AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 213, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAM0AAAAAAAAAzwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzQAAAAAAAADNAAAAAAAAAAAAAAAAAPC/" - ], - [ - 221, - 1, - "paste", - null, - "AgAAAOkAAAAAAAAA9wAAAAAAAAAAAAAA9wAAAAAAAAD3AAAAAAAAAAQAAABOb25l", - "AQAAAAAAAAABAAAA7QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 223, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADwEAAAAAAAAPAQAAAAAAAAAAAAAA8HVA" - ], - [ - 224, - 1, - "insert", - { - "characters": " " - }, - "AQAAABEBAAAAAAAAEgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" - ], - [ - 225, - 1, - "paste", - null, - "AQAAABIBAAAAAAAAIAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 227, - 1, - "insert", - { - "characters": " =" - }, - "AgAAADUBAAAAAAAANgEAAAAAAAAAAAAANgEAAAAAAAA3AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAgHZA" - ], - [ - 228, - 1, - "insert", - { - "characters": " " - }, - "AQAAADcBAAAAAAAAOAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 229, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAADgBAAAAAAAAOgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" - ], - [ - 231, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAE8BAAAAAAAAUAEAAAAAAAAAAAAAUAEAAAAAAABRAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATwEAAAAAAABPAQAAAAAAAAAAAAAAAGtA" - ], - [ - 232, - 1, - "insert", - { - "characters": " " - }, - "AQAAAFEBAAAAAAAAUgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUQEAAAAAAABRAQAAAAAAAAAAAAAAAPC/" - ], - [ - 233, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAFIBAAAAAAAAVAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUgEAAAAAAABSAQAAAAAAAAAAAAAAAPC/" - ], - [ - 237, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAG0BAAAAAAAAbgEAAAAAAAAAAAAAbgEAAAAAAABvAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbQEAAAAAAABtAQAAAAAAAAAAAAAAAPC/" - ], - [ - 238, - 1, - "insert", - { - "characters": " " - }, - "AQAAAG8BAAAAAAAAcAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbwEAAAAAAABvAQAAAAAAAAAAAAAAAPC/" - ], - [ - 239, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAHABAAAAAAAAcgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcAEAAAAAAABwAQAAAAAAAAAAAAAAAPC/" - ], - [ - 241, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAIMBAAAAAAAAhAEAAAAAAAAAAAAAhAEAAAAAAACFAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgwEAAAAAAACDAQAAAAAAAAAAAAAAgG9A" - ], - [ - 242, - 1, - "insert", - { - "characters": " " - }, - "AQAAAIUBAAAAAAAAhgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQEAAAAAAACFAQAAAAAAAAAAAAAAAPC/" - ], - [ - 243, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAIYBAAAAAAAAiAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" - ], - [ - 249, - 1, - "insert", - { - "characters": "None" - }, - "BQAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADCAAAAAAAAAA4AAABkYXRldGltZS5ub3coKcIAAAAAAAAAwwAAAAAAAAAAAAAAwwAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMUAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQAAAAAAAADPAAAAAAAAAAAAAAAAAPC/" - ], - [ - 258, - 1, - "insert", - { - "characters": "Optional(" - }, - "CQAAALYAAAAAAAAAtwAAAAAAAAAAAAAAtwAAAAAAAAC4AAAAAAAAAAAAAAC4AAAAAAAAALkAAAAAAAAAAAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAAvQAAAAAAAAC+AAAAAAAAAAAAAAC+AAAAAAAAAL8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtgAAAAAAAAC2AAAAAAAAAAAAAAAAAPC/" - ], - [ - 259, - 1, - "left_delete", - null, - "AQAAAL4AAAAAAAAAvgAAAAAAAAABAAAAKA", - "AQAAAAAAAAABAAAAvwAAAAAAAAC/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 260, - 1, - "insert", - { - "characters": "[" - }, - "AQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgAAAAAAAAC+AAAAAAAAAAAAAAAAAPC/" - ], - [ - 262, - 1, - "insert", - { - "characters": "]" - }, - "AQAAAMcAAAAAAAAAyAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxwAAAAAAAADHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 267, - 1, - "", - null, - "AQAAAGsAAAAAAAAAhgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 278, - 1, - "paste", - null, - "AgAAAPkAAAAAAAAACwEAAAAAAAAAAAAACwEAAAAAAAALAQAAAAAAAAgAAABkYXRldGltZQ", - "AQAAAAAAAAABAAAA+QAAAAAAAAABAQAAAAAAAAAAAAAAAPC/" - ], - [ - 283, - 1, - "paste", - null, - "AgAAACwBAAAAAAAAPgEAAAAAAAAAAAAAPgEAAAAAAAA+AQAAAAAAAAgAAABkYXRldGltZQ", - "AQAAAAAAAAABAAAALAEAAAAAAAA0AQAAAAAAAAAAAAAAAPC/" - ], - [ - 287, - 1, - "insert", - { - "characters": "None" - }, - "BQAAAA4BAAAAAAAADwEAAAAAAAAAAAAADwEAAAAAAAAPAQAAAAAAAA4AAABkYXRldGltZS5ub3coKQ8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAARAQAAAAAAABIBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADgEAAAAAAAAcAQAAAAAAAP///////+9/" - ], - [ - 291, - 1, - "insert", - { - "characters": "NOne" - }, - "BQAAADcBAAAAAAAAOAEAAAAAAAAAAAAAOAEAAAAAAAA4AQAAAAAAAA4AAABkYXRldGltZS5ub3coKTgBAAAAAAAAOQEAAAAAAAAAAAAAOQEAAAAAAAA6AQAAAAAAAAAAAAA6AQAAAAAAADsBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANwEAAAAAAABFAQAAAAAAAP///////+9/" - ], - [ - 294, - 1, - "insert", - { - "characters": "i" - }, - "AgAAADgBAAAAAAAAOQEAAAAAAAAAAAAAOQEAAAAAAAA5AQAAAAAAAAEAAABP", - "AQAAAAAAAAABAAAAOQEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" - ], - [ - 295, - 1, - "left_delete", - null, - "AQAAADgBAAAAAAAAOAEAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" - ], - [ - 296, - 1, - "insert", - { - "characters": "o" - }, - "AQAAADgBAAAAAAAAOQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" - ], - [ - 300, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAACkAQAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmZyb20gZGF0YWNsYXNzZXMgaW1wb3J0IGRhdGFjbGFzcwpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQpmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwKCkBkYXRhY2xhc3MKY2xhc3MgQ29tbWVudDoKICAgIGlkOiBpbnQgPSAwCiAgICB1cmw6IHN0ciA9ICIiCiAgICBjcmVhdGVkOiBPcHRpb25hbFtkYXRldGltZV0gPSBOb25lCiAgICBub3RpZmllZDogT3B0aW9uYWxbZGF0ZXRpbWVdID0gTm9uZQogICAgcHVibGlzaGVkOiBPcHRpb25hbFtkYXRldGltZV0gPSBOb25lCiAgICBhdXRob3JfbmFtZTogc3RyID0gIiIKICAgIGF1dGhvcl9zaXRlOiBzdHIgPSAiIgogICAgYXV0aG9yX2dyYXZhdGFyOiBzdHIgPSAiIgogICAgY29udGVudDogc3RyID0gIiIKAAAAAAAAAAClAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVQEAAAAAAABVAQAAAAAAAAAAAAAAYHVA" - ] - ] - }, - { - "file": "tests/test_api.py", - "settings": - { - "buffer_size": 1655, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "insert", - { - "characters": "configure" - }, - "CgAAAFkCAAAAAAAAWgIAAAAAAAAAAAAAWgIAAAAAAABaAgAAAAAAAAUAAABzZXR1cFoCAAAAAAAAWwIAAAAAAAAAAAAAWwIAAAAAAABcAgAAAAAAAAAAAABcAgAAAAAAAF0CAAAAAAAAAAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAGACAAAAAAAAYQIAAAAAAAAAAAAAYQIAAAAAAABiAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAWQIAAAAAAABeAgAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "insert", - { - "characters": "sqlite://" - }, - "CQAAAGQCAAAAAAAAZQIAAAAAAAAAAAAAZQIAAAAAAABmAgAAAAAAAAAAAABmAgAAAAAAAGcCAAAAAAAAAAAAAGcCAAAAAAAAaAIAAAAAAAAAAAAAaAIAAAAAAABpAgAAAAAAAAAAAABpAgAAAAAAAGoCAAAAAAAAAAAAAGoCAAAAAAAAawIAAAAAAAAAAAAAawIAAAAAAABsAgAAAAAAAAAAAABsAgAAAAAAAG0CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZAIAAAAAAABkAgAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": "memory" - }, - "BgAAAGsCAAAAAAAAbAIAAAAAAAAAAAAAbAIAAAAAAABtAgAAAAAAAAAAAABtAgAAAAAAAG4CAAAAAAAAAAAAAG4CAAAAAAAAbwIAAAAAAAAAAAAAbwIAAAAAAABwAgAAAAAAAAAAAABwAgAAAAAAAHECAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAawIAAAAAAABrAgAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "insert", - { - "characters": "dummy." - }, - "BwAAAHMCAAAAAAAAdAIAAAAAAAAAAAAAdAIAAAAAAAB0AgAAAAAAAAgAAAA6bWVtb3J5OnQCAAAAAAAAdQIAAAAAAAAAAAAAdQIAAAAAAAB2AgAAAAAAAAAAAAB2AgAAAAAAAHcCAAAAAAAAAAAAAHcCAAAAAAAAeAIAAAAAAAAAAAAAeAIAAAAAAAB5AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcwIAAAAAAAB7AgAAAAAAAAAAAAAAAPC/" - ], - [ - 16, - 1, - "insert", - { - "characters": "db" - }, - "AgAAAHkCAAAAAAAAegIAAAAAAAAAAAAAegIAAAAAAAB7AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAeQIAAAAAAAB5AgAAAAAAAAAAAAAAAPC/" - ], - [ - 19, - 1, - "insert", - { - "characters": "db" - }, - "AwAAAHMCAAAAAAAAdAIAAAAAAAAAAAAAdAIAAAAAAAB0AgAAAAAAAAUAAABkdW1teXQCAAAAAAAAdQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAeAIAAAAAAABzAgAAAAAAAAAAAAAAAPC/" - ], - [ - 22, - 1, - "insert", - { - "characters": "sqlite" - }, - "BwAAAHYCAAAAAAAAdwIAAAAAAAAAAAAAdwIAAAAAAAB3AgAAAAAAAAIAAABkYncCAAAAAAAAeAIAAAAAAAAAAAAAeAIAAAAAAAB5AgAAAAAAAAAAAAB5AgAAAAAAAHoCAAAAAAAAAAAAAHoCAAAAAAAAewIAAAAAAAAAAAAAewIAAAAAAAB8AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgIAAAAAAAB4AgAAAAAAAAAAAAAAAPC/" - ], - [ - 31, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAHECAAAAAAAAcgIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcQIAAAAAAABxAgAAAAAAAAAAAAAAAPC/" - ], - [ - 71, - 1, - "toggle_breakpoint", - null, - "AQAAAL0BAAAAAAAA9wEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" - ], - [ - 74, - 1, - "toggle_breakpoint", - null, - "AQAAAL0BAAAAAAAAvQEAAAAAAAA6AAAAICAgIGltcG9ydCBwZGI7IHBkYi5zZXRfdHJhY2UoKSAgIyBicmVha3BvaW50IDU1ZGNmYmI4IC8vCg", - "AQAAAAAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "tests/test_db.py", - "settings": - { - "buffer_size": 1835, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 6, - 1, - "insert", - { - "characters": "configure" - }, - "CgAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACmAAAAAAAAAAUAAABzZXR1cKYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApQAAAAAAAACqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": "sqlite" - }, - "BgAAALAAAAAAAAAAsQAAAAAAAAAAAAAAsQAAAAAAAACyAAAAAAAAAAAAAACyAAAAAAAAALMAAAAAAAAAAAAAALMAAAAAAAAAtAAAAAAAAAAAAAAAtAAAAAAAAAC1AAAAAAAAAAAAAAC1AAAAAAAAALYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAPC/" - ], - [ - 14, - 1, - "insert", - { - "characters": "//db." - }, - "BQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAMEAAAAAAAAAAAAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADDAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgAAAAAAAAC+AAAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "insert", - { - "characters": "sqlite" - }, - "BgAAAMMAAAAAAAAAxAAAAAAAAAAAAAAAxAAAAAAAAADFAAAAAAAAAAAAAADFAAAAAAAAAMYAAAAAAAAAAAAAAMYAAAAAAAAAxwAAAAAAAAAAAAAAxwAAAAAAAADIAAAAAAAAAAAAAADIAAAAAAAAAMkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwwAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" - ], - [ - 17, - 1, - "trim_trailing_white_space", - null, - "AQAAAMsAAAAAAAAAywAAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAyQAAAAAAAADJAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "tests/test_form.py", - "settings": - { - "buffer_size": 1125, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 6, - 1, - "insert", - { - "characters": "configure" - }, - "CgAAAAgBAAAAAAAACQEAAAAAAAAAAAAACQEAAAAAAAAJAQAAAAAAAAUAAABzZXR1cAkBAAAAAAAACgEAAAAAAAAAAAAACgEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACAEAAAAAAAANAQAAAAAAAAAAAAAAAPC/" - ], - [ - 8, - 1, - "insert", - { - "characters": "q" - }, - "AQAAABMBAAAAAAAAFAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" - ], - [ - 9, - 1, - "left_delete", - null, - "AQAAABMBAAAAAAAAEwEAAAAAAAABAAAAcQ", - "AQAAAAAAAAABAAAAFAEAAAAAAAAUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 10, - 1, - "insert", - { - "characters": "sqlite" - }, - "BgAAABMBAAAAAAAAFAEAAAAAAAAAAAAAFAEAAAAAAAAVAQAAAAAAAAAAAAAVAQAAAAAAABYBAAAAAAAAAAAAABYBAAAAAAAAFwEAAAAAAAAAAAAAFwEAAAAAAAAYAQAAAAAAAAAAAAAYAQAAAAAAABkBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": "//db.sqli" - }, - "CQAAACEBAAAAAAAAIgEAAAAAAAAAAAAAIgEAAAAAAAAjAQAAAAAAAAAAAAAjAQAAAAAAACQBAAAAAAAAAAAAACQBAAAAAAAAJQEAAAAAAAAAAAAAJQEAAAAAAAAmAQAAAAAAAAAAAAAmAQAAAAAAACcBAAAAAAAAAAAAACcBAAAAAAAAKAEAAAAAAAAAAAAAKAEAAAAAAAApAQAAAAAAAAAAAAApAQAAAAAAACoBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIQEAAAAAAAAhAQAAAAAAAAAAAAAAAPC/" - ], - [ - 14, - 1, - "insert", - { - "characters": "te" - }, - "AgAAACoBAAAAAAAAKwEAAAAAAAAAAAAAKwEAAAAAAAAsAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKgEAAAAAAAAqAQAAAAAAAAAAAAAAAPC/" - ], - [ - 16, - 1, - "trim_trailing_white_space", - null, - "AQAAAC4BAAAAAAAALgEAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAALAEAAAAAAAAsAQAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "config.ini", - "settings": - { - "buffer_size": 500, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 3, - 1, - "paste", - null, - "AQAAAEYAAAAAAAAAXQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABGAAAAAAAAAEYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 4, + 439, 1, "insert", { "characters": "\n" }, - "AQAAAF0AAAAAAAAAXgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAABdAAAAAAAAAF0AAAAAAAAAAAAAAAAA8L8" + "AgAAAIgBAAAAAAAAiQEAAAAAAAAAAAAAiQEAAAAAAACRAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAiAEAAAAAAACIAQAAAAAAAAAAAAAAAPC/" ], [ - 4, - 1, - "right_delete", - null, - "AQAAACsAAAAAAAAAKwAAAAAAAAAaAAAAZGJfc3FsaXRlX2ZpbGUgPSBkYi5zcWxpdGU", - "AQAAAAAAAAABAAAARQAAAAAAAAArAAAAAAAAAAAAAAAAAAAA" - ], - [ - 5, - 1, - "left_delete", - null, - "AQAAACoAAAAAAAAAKgAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAKwAAAAAAAAArAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "stacosys/service/configuration.py", - "settings": - { - "buffer_size": 2563, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 6, + 442, 1, "insert", { - "characters": "\nDB" + "characters": "\\" }, - "BAAAAHcAAAAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAB8AAAAAAAAAAAAAAB8AAAAAAAAAH0AAAAAAAAAAAAAAH0AAAAAAAAAfgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB3AAAAAAAAAHcAAAAAAAAAAAAAAAAA8L8" + "AQAAAL4BAAAAAAAAvwEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" ], [ - 7, + 443, 1, "insert", { - "characters": " =" + "characters": "\n" }, - "AgAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB+AAAAAAAAAH4AAAAAAAAAAAAAAAAA8L8" + "AgAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADIAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAPC/" ], [ - 8, + 446, + 1, + "insert", + { + "characters": "\\" + }, + "AQAAAPUBAAAAAAAA9gEAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA9QEAAAAAAAD1AQAAAAAAAAAAAAAAAPC/" + ], + [ + 447, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAPYBAAAAAAAA9wEAAAAAAAAAAAAA9wEAAAAAAAD/AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA9gEAAAAAAAD2AQAAAAAAAAAAAAAAAPC/" + ], + [ + 450, + 1, + "insert", + { + "characters": "\\" + }, + "AQAAACQCAAAAAAAAJQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJAIAAAAAAAAkAgAAAAAAAAAAAAAAAPC/" + ], + [ + 451, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAACUCAAAAAAAAJgIAAAAAAAAAAAAAJgIAAAAAAAAuAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJQIAAAAAAAAlAgAAAAAAAAAAAAAAAPC/" + ], + [ + 454, + 1, + "insert", + { + "characters": "\\" + }, + "AQAAAFMCAAAAAAAAVAIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAUwIAAAAAAABTAgAAAAAAAAAAAAAAAPC/" + ], + [ + 455, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAFQCAAAAAAAAVQIAAAAAAAAAAAAAVQIAAAAAAABdAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAVAIAAAAAAABUAgAAAAAAAAAAAAAAAPC/" + ], + [ + 458, + 1, + "insert", + { + "characters": "\\" + }, + "AQAAAIQCAAAAAAAAhQIAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhAIAAAAAAACEAgAAAAAAAAAAAAAAAPC/" + ], + [ + 459, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACOAgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhQIAAAAAAACFAgAAAAAAAAAAAAAAAPC/" + ], + [ + 493, + 1, + "insert", + { + "characters": "\nass" + }, + "BQAAALQEAAAAAAAAtQQAAAAAAAAAAAAAtQQAAAAAAAC5BAAAAAAAAAAAAAC5BAAAAAAAALoEAAAAAAAAAAAAALoEAAAAAAAAuwQAAAAAAAAAAAAAuwQAAAAAAAC8BAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAtAQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" + ], + [ + 494, + 1, + "insert", + { + "characters": "ert" + }, + "AwAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvAQAAAAAAAC8BAAAAAAAAAAAAAAAAPC/" + ], + [ + 495, + 1, + "insert", + { + "characters": " 1" + }, + "AgAAAL8EAAAAAAAAwAQAAAAAAAAAAAAAwAQAAAAAAADBBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAvwQAAAAAAAC/BAAAAAAAAAAAAAAAAPC/" + ], + [ + 496, + 1, + "insert", + { + "characters": " ==" + }, + "AwAAAMEEAAAAAAAAwgQAAAAAAAAAAAAAwgQAAAAAAADDBAAAAAAAAAAAAADDBAAAAAAAAMQEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAwQQAAAAAAADBBAAAAAAAAAAAAAAAAPC/" + ], + [ + 497, 1, "insert", { "characters": " " }, - "AQAAAIAAAAAAAAAAgQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAA8L8" + "AQAAAMQEAAAAAAAAxQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxAQAAAAAAADEBAAAAAAAAAAAAAAAAPC/" ], [ - 9, + 498, + 1, + "paste", + null, + "AQAAAMUEAAAAAAAA4AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" + ], + [ + 502, + 1, + "insert", + { + "characters": "dao." + }, + "BAAAAMUEAAAAAAAAxgQAAAAAAAAAAAAAxgQAAAAAAADHBAAAAAAAAAAAAADHBAAAAAAAAMgEAAAAAAAAAAAAAMgEAAAAAAAAyQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" + ], + [ + 504, + 1, + "insert_snippet", + { + "contents": "($0)" + }, + "AQAAAOQEAAAAAAAA5gQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5AQAAAAAAADkBAAAAAAAAP///////+9/" + ], + [ + 505, 1, "insert_snippet", { "contents": "\"$0\"" }, - "AQAAAIEAAAAAAAAAgwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACBAAAAAAAAAIEAAAAAAAAAAAAAAAAA8L8" + "AQAAAOUEAAAAAAAA5wQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA5QQAAAAAAADlBAAAAAAAAAAAAAAAAPC/" ], [ - 10, + 509, + 2, + "left_delete", + null, + "AgAAAOYEAAAAAAAA5gQAAAAAAAABAAAAIuUEAAAAAAAA5QQAAAAAAAABAAAAIg", + "AQAAAAAAAAABAAAA5wQAAAAAAADnBAAAAAAAAAAAAAAAAPC/" + ], + [ + 514, 1, "insert", { - "characters": "main.db" + "characters": "len(" }, - "BwAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACCAAAAAAAAAIIAAAAAAAAAAAAAAAAA8L8" + "BAAAAMUEAAAAAAAAxgQAAAAAAAAAAAAAxgQAAAAAAADHBAAAAAAAAAAAAADHBAAAAAAAAMgEAAAAAAAAAAAAAMgEAAAAAAAAyQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" ], [ - 8, + 516, 1, - "right_delete", - null, - "AQAAAI8AAAAAAAAAjwAAAAAAAAAmAAAAREJfU1FMSVRFX0ZJTEUgPSAibWFpbi5kYl9zcWxpdGVfZmlsZSI", - "AQAAAAAAAAABAAAAtQAAAAAAAACPAAAAAAAAAAAAAAAAAEJA" + "insert", + { + "characters": ")" + }, + "AQAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAP///////+9/" ], [ - 10, + 523, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAACUGAAAAAAAAJgYAAAAAAAAAAAAAJgYAAAAAAAAqBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJQYAAAAAAAAlBgAAAAAAAAAAAAAAAPC/" + ], + [ + 524, + 1, + "insert", + { + "characters": "\n" + }, + "AwAAACoGAAAAAAAAKwYAAAAAAAAAAAAAKwYAAAAAAAAvBgAAAAAAAAAAAAAmBgAAAAAAACYGAAAAAAAABAAAACAgICA", + "AQAAAAAAAAABAAAAKgYAAAAAAAAqBgAAAAAAAAAAAAAAAPC/" + ], + [ + 525, + 1, + "insert", + { + "characters": "#" + }, + "AQAAACsGAAAAAAAALAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKwYAAAAAAAArBgAAAAAAAAAAAAAAAPC/" + ], + [ + 526, + 1, + "insert", + { + "characters": " count" + }, + "BgAAACwGAAAAAAAALQYAAAAAAAAAAAAALQYAAAAAAAAuBgAAAAAAAAAAAAAuBgAAAAAAAC8GAAAAAAAAAAAAAC8GAAAAAAAAMAYAAAAAAAAAAAAAMAYAAAAAAAAxBgAAAAAAAAAAAAAxBgAAAAAAADIGAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALAYAAAAAAAAsBgAAAAAAAAAAAAAAAPC/" + ], + [ + 527, + 1, + "insert", + { + "characters": " published" + }, + "CgAAADIGAAAAAAAAMwYAAAAAAAAAAAAAMwYAAAAAAAA0BgAAAAAAAAAAAAA0BgAAAAAAADUGAAAAAAAAAAAAADUGAAAAAAAANgYAAAAAAAAAAAAANgYAAAAAAAA3BgAAAAAAAAAAAAA3BgAAAAAAADgGAAAAAAAAAAAAADgGAAAAAAAAOQYAAAAAAAAAAAAAOQYAAAAAAAA6BgAAAAAAAAAAAAA6BgAAAAAAADsGAAAAAAAAAAAAADsGAAAAAAAAPAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" + ], + [ + 536, + 1, + "paste", + null, + "AQAAAKsGAAAAAAAA3QYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAYG5A" + ], + [ + 538, + 1, + "insert", + { + "characters": "\t" + }, + "AQAAAKsGAAAAAAAArwYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAAAA" + ], + [ + 542, + 1, + "insert", + { + "characters": "3" + }, + "AgAAALYGAAAAAAAAtwYAAAAAAAAAAAAAtwYAAAAAAAC3BgAAAAAAAAEAAAAx", + "AQAAAAAAAAABAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" + ], + [ + 544, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAOEGAAAAAAAA4gYAAAAAAAAAAAAA4gYAAAAAAADmBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4QYAAAAAAADhBgAAAAAAAP///////+9/" + ], + [ + 549, + 1, + "insert", + { + "characters": "0" + }, + "AwAAALYGAAAAAAAAtwYAAAAAAAAAAAAAtwYAAAAAAAC3BgAAAAAAAAEAAAAz4gYAAAAAAADiBgAAAAAAAAQAAAAgICAg", + "AQAAAAAAAAABAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" + ], + [ + 554, + 1, + "cut", + null, + "AQAAAK8GAAAAAAAArwYAAAAAAAAyAAAAYXNzZXJ0IDAgPT0gbGVuKGRhby5maW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKSk", + "AQAAAAAAAAABAAAArwYAAAAAAADhBgAAAAAAAP///////+9/" + ], + [ + 556, 1, "left_delete", null, - "AQAAAIoAAAAAAAAAigAAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAAAA" + "AQAAAKoGAAAAAAAAqgYAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAAAA" ], [ - 12, + 558, + 1, + "paste", + null, + "AQAAACYGAAAAAAAAWAYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJgYAAAAAAAAmBgAAAAAAAAAAAAAAYH5A" + ], + [ + 560, + 1, + "insert", + { + "characters": "\t" + }, + "AQAAACYGAAAAAAAAKgYAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAJgYAAAAAAAAmBgAAAAAAAAAAAAAAAAAA" + ], + [ + 562, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAFwGAAAAAAAAXQYAAAAAAAAAAAAAXQYAAAAAAABhBgAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAXAYAAAAAAABcBgAAAAAAAP///////+9/" + ], + [ + 564, 1, "trim_trailing_white_space", null, - "AQAAAIoAAAAAAAAAigAAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" + "AgAAAOUGAAAAAAAA5QYAAAAAAAAEAAAAICAgIF0GAAAAAAAAXQYAAAAAAAAEAAAAICAgIA", + "AQAAAAAAAAABAAAAYQYAAAAAAABhBgAAAAAAAAAAAAAAAPC/" + ], + [ + 571, + 2, + "right_delete", + null, + "AgAAAOkGAAAAAAAA6QYAAAAAAAAEAAAAdGVzdOkGAAAAAAAA6QYAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAA6QYAAAAAAADtBgAAAAAAAAAAAAAAAPC/" + ], + [ + 583, + 1, + "reindent", + null, + "AQAAANEDAAAAAAAA1QMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0QMAAAAAAADRAwAAAAAAAAAAAAAAAPC/" + ], + [ + 584, + 1, + "paste", + null, + "AQAAANUDAAAAAAAA6wMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1QMAAAAAAADVAwAAAAAAAAAAAAAAAPC/" + ], + [ + 597, + 1, + "insert", + { + "characters": "\nc1" + }, + "BAAAANADAAAAAAAA0QMAAAAAAAAAAAAA0QMAAAAAAADVAwAAAAAAAAAAAADVAwAAAAAAANYDAAAAAAAAAAAAANYDAAAAAAAA1wMAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA0AMAAAAAAADQAwAAAAAAAP///////+9/" + ], + [ + 598, + 1, + "insert", + { + "characters": ".id" + }, + "AwAAANcDAAAAAAAA2AMAAAAAAAAAAAAA2AMAAAAAAADZAwAAAAAAAAAAAADZAwAAAAAAANoDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA1wMAAAAAAADXAwAAAAAAAAAAAAAAAPC/" + ], + [ + 599, + 1, + "insert", + { + "characters": " =" + }, + "AgAAANoDAAAAAAAA2wMAAAAAAAAAAAAA2wMAAAAAAADcAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA2gMAAAAAAADaAwAAAAAAAAAAAAAAAPC/" + ], + [ + 600, + 1, + "insert", + { + "characters": " find" + }, + "BQAAANwDAAAAAAAA3QMAAAAAAAAAAAAA3QMAAAAAAADeAwAAAAAAAAAAAADeAwAAAAAAAN8DAAAAAAAAAAAAAN8DAAAAAAAA4AMAAAAAAAAAAAAA4AMAAAAAAADhAwAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA3AMAAAAAAADcAwAAAAAAAAAAAAAAAPC/" + ], + [ + 601, + 1, + "insert", + { + "characters": "_c1.id" + }, + "BgAAAOEDAAAAAAAA4gMAAAAAAAAAAAAA4gMAAAAAAADjAwAAAAAAAAAAAADjAwAAAAAAAOQDAAAAAAAAAAAAAOQDAAAAAAAA5QMAAAAAAAAAAAAA5QMAAAAAAADmAwAAAAAAAAAAAADmAwAAAAAAAOcDAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAA4QMAAAAAAADhAwAAAAAAAAAAAAAAAPC/" + ], + [ + 604, + 1, + "insert", + { + "characters": "\n" + }, + "AgAAAAIEAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAAHBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAgQAAAAAAAACBAAAAAAAAAAAAAAAAPC/" + ], + [ + 609, + 1, + "paste", + null, + "AQAAAAcEAAAAAAAALgQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABwQAAAAAAAAHBAAAAAAAAAAAAAAAQGRA" + ], + [ + 613, + 1, + "insert", + { + "characters": "assre" + }, + "BgAAAAcEAAAAAAAACAQAAAAAAAAAAAAACAQAAAAAAAAIBAAAAAAAAAkAAABmaW5kX2MxID0IBAAAAAAAAAkEAAAAAAAAAAAAAAkEAAAAAAAACgQAAAAAAAAAAAAACgQAAAAAAAALBAAAAAAAAAAAAAALBAAAAAAAAAwEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAABwQAAAAAAAAQBAAAAAAAAAAAAAAAAPC/" + ], + [ + 614, + 1, + "left_delete", + null, + "AQAAAAsEAAAAAAAACwQAAAAAAAABAAAAZQ", + "AQAAAAAAAAABAAAADAQAAAAAAAAMBAAAAAAAAAAAAAAAAPC/" + ], + [ + 615, + 1, + "insert", + { + "characters": "e" + }, + "AQAAAAsEAAAAAAAADAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACwQAAAAAAAALBAAAAAAAAAAAAAAAAPC/" + ], + [ + 616, + 2, + "left_delete", + null, + "AgAAAAsEAAAAAAAACwQAAAAAAAABAAAAZQoEAAAAAAAACgQAAAAAAAABAAAAcg", + "AQAAAAAAAAABAAAADAQAAAAAAAAMBAAAAAAAAAAAAAAAAPC/" + ], + [ + 617, + 1, + "insert", + { + "characters": "ert" + }, + "AwAAAAoEAAAAAAAACwQAAAAAAAAAAAAACwQAAAAAAAAMBAAAAAAAAAAAAAAMBAAAAAAAAA0EAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAACgQAAAAAAAAKBAAAAAAAAAAAAAAAAPC/" + ], + [ + 619, + 1, + "insert", + { + "characters": " is" + }, + "AwAAACsEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAtBAAAAAAAAAAAAAAtBAAAAAAAAC4EAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAKwQAAAAAAAArBAAAAAAAAP///////+9/" + ], + [ + 620, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAC4EAAAAAAAALwQAAAAAAAAAAAAALwQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADEEAAAAAAAAAAAAADEEAAAAAAAAMgQAAAAAAAAAAAAAMgQAAAAAAAAzBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALgQAAAAAAAAuBAAAAAAAAAAAAAAAAPC/" + ], + [ + 622, + 1, + "right_delete", + null, + "AQAAACwEAAAAAAAALAQAAAAAAAAHAAAAaXMgTm9uZQ", + "AQAAAAAAAAABAAAAMwQAAAAAAAAsBAAAAAAAAAAAAAAAAPC/" + ], + [ + 624, + 1, + "insert", + { + "characters": "not" + }, + "AwAAAA4EAAAAAAAADwQAAAAAAAAAAAAADwQAAAAAAAAQBAAAAAAAAAAAAAAQBAAAAAAAABEEAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAADgQAAAAAAAAOBAAAAAAAAAAAAAAAAPC/" + ], + [ + 625, + 1, + "insert", + { + "characters": " " + }, + "AQAAABEEAAAAAAAAEgQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAEQQAAAAAAAARBAAAAAAAAAAAAAAAAPC/" + ], + [ + 627, + 1, + "right_delete", + null, + "AQAAAA4EAAAAAAAADgQAAAAAAAAEAAAAbm90IA", + "AQAAAAAAAAABAAAAEgQAAAAAAAAOBAAAAAAAAAAAAAAAAPC/" + ], + [ + 629, + 1, + "insert", + { + "characters": " " + }, + "AQAAACwEAAAAAAAALQQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALAQAAAAAAAAsBAAAAAAAAP///////+9/" + ], + [ + 630, + 1, + "left_delete", + null, + "AQAAACwEAAAAAAAALAQAAAAAAAABAAAAIA", + "AQAAAAAAAAABAAAALQQAAAAAAAAtBAAAAAAAAAAAAAAAAPC/" + ], + [ + 631, + 1, + "insert", + { + "characters": "is" + }, + "AgAAACwEAAAAAAAALQQAAAAAAAAAAAAALQQAAAAAAAAuBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALAQAAAAAAAAsBAAAAAAAAAAAAAAAAPC/" + ], + [ + 632, + 1, + "insert", + { + "characters": " None" + }, + "BQAAAC4EAAAAAAAALwQAAAAAAAAAAAAALwQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADEEAAAAAAAAAAAAADEEAAAAAAAAMgQAAAAAAAAAAAAAMgQAAAAAAAAzBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALgQAAAAAAAAuBAAAAAAAAAAAAAAAAPC/" + ], + [ + 638, + 2, + "left_delete", + null, + "AgAAABwIAAAAAAAAHAgAAAAAAABcAAAAICAgIGRhby5kZWxldGVfY29tbWVudChjMSkKICAgIGFzc2VydCAwID09IGxlbihkYW8uZmluZF9wdWJsaXNoZWRfY29tbWVudHNfYnlfdXJsKCIvcG9zdDEiKSkbCAAAAAAAABsIAAAAAAAAAQAAAAo", + "AQAAAAAAAAABAAAAeAgAAAAAAAAcCAAAAAAAAAAAAAAAAPC/" + ], + [ + 641, + 1, + "left_delete", + null, + "AQAAABoIAAAAAAAAGggAAAAAAAABAAAACg", + "AQAAAAAAAAABAAAAGwgAAAAAAAAbCAAAAAAAAAAAAAAAAPC/" ] ] }, { - "file": "tests/test_config.py", + "contents": "Package Control Messages\n========================\n\npython-black\n------------\n\n Thanks for trying out the python-black package!\n ===============================================\n\n\n `python-black` is an unofficial `black` plugin developed for Sublime Text 4.\n\n The current version can format the currently active `.py` file before saving.\n\n The configuration file used is the same as the official `black`.\n\n **Sublime Text needs to be restarted after installing `python-black`!**", "settings": { - "buffer_size": 1113, + "buffer_size": 506, + "line_ending": "Unix", + "name": "Package Control Messages", + "read_only": true, + "scratch": true + }, + "undo_stack": + [ + [ + 1, + 1, + "insert", + { + "characters": "Package Control Messages\n========================\n\npython-black\n------------\n\n Thanks for trying out the python-black package!\n ===============================================\n\n\n `python-black` is an unofficial `black` plugin developed for Sublime Text 4.\n\n The current version can format the currently active `.py` file before saving.\n\n The configuration file used is the same as the official `black`.\n\n **Sublime Text needs to be restarted after installing `python-black`!**" + }, + "GgAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMgAAAAAAAAAAAAAAMgAAAAAAAAAzAAAAAAAAAAAAAAAzAAAAAAAAAD8AAAAAAAAAAAAAAD8AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAABMAAAAAAAAAAAAAABMAAAAAAAAAE0AAAAAAAAAAAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAyQAAAAAAAAAAAAAAyQAAAAAAAADKAAAAAAAAAAAAAADKAAAAAAAAAMsAAAAAAAAAAAAAAMsAAAAAAAAAzAAAAAAAAAAAAAAAzAAAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAABsBAAAAAAAAHAEAAAAAAAAAAAAAHAEAAAAAAABrAQAAAAAAAAAAAABrAQAAAAAAAGwBAAAAAAAAAAAAAGwBAAAAAAAAbQEAAAAAAAAAAAAAbQEAAAAAAACvAQAAAAAAAAAAAACvAQAAAAAAALABAAAAAAAAAAAAALABAAAAAAAAsQEAAAAAAAAAAAAAsQEAAAAAAAD6AQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/" + ] + ] + }, + { + "file": "Makefile", + "settings": + { + "buffer_size": 338, "encoding": "UTF-8", "line_ending": "Unix" }, "undo_stack": [ [ - 3, + 8, 1, "insert", { - "characters": "sqlite:/" + "characters": " tests/" }, - "CAAAALUAAAAAAAAAtgAAAAAAAAAAAAAAtgAAAAAAAAC3AAAAAAAAAAAAAAC3AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAC6AAAAAAAAALsAAAAAAAAAAAAAALsAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAAC9AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtQAAAAAAAAC1AAAAAAAAAAAAAAAAAPC/" + "BwAAAGEAAAAAAAAAYgAAAAAAAAAAAAAAYgAAAAAAAABjAAAAAAAAAAAAAABjAAAAAAAAAGQAAAAAAAAAAAAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAYQAAAAAAAABhAAAAAAAAAP///////+9/" ], [ - 4, + 10, 1, "insert", { - "characters": "/" + "characters": " tests/" }, - "AQAAAL0AAAAAAAAAvgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvQAAAAAAAAC9AAAAAAAAAAAAAAAAAPC/" - ], - [ - 12, - 1, - "right_delete", - null, - "AQAAAD4BAAAAAAAAPgEAAAAAAAAMAAAAX1NRTElURV9GSUxF", - "AQAAAAAAAAABAAAAPgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" - ], - [ - 20, - 1, - "right_delete", - null, - "AQAAAOUBAAAAAAAA5QEAAAAAAAAMAAAAX1NRTElURV9GSUxF", - "AQAAAAAAAAABAAAA5QEAAAAAAADxAQAAAAAAAAAAAAAAAPC/" - ], - [ - 24, - 1, - "right_delete", - null, - "AQAAACsCAAAAAAAAKwIAAAAAAAAMAAAAX1NRTElURV9GSUxF", - "AQAAAAAAAAABAAAAKwIAAAAAAAA3AgAAAAAAAAAAAAAAAPC/" - ], - [ - 31, - 1, - "right_delete", - null, - "AQAAAGgDAAAAAAAAaAMAAAAAAAAMAAAAX1NRTElURV9GSUxF", - "AQAAAAAAAAABAAAAaAMAAAAAAAB0AwAAAAAAAAAAAAAAAPC/" + "BwAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAACGAAAAAAAAAIcAAAAAAAAAAAAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAIINA" ] ] + }, + { + "file": "stacosys/db/dao.py", + "settings": + { + "buffer_size": 2026, + "line_ending": "Unix" + }, + "undo_stack": + [ + ] } ], "build_system": "", @@ -8316,6 +6145,10 @@ "last_filter": "", "selected_items": [ + [ + "insta", + "Package Control: Install Package" + ], [ "break", "Python Breakpoints: Toggle" @@ -8368,10 +6201,6 @@ "python", "Python Breakpoints: Toggle" ], - [ - "insta", - "Package Control: Install Package" - ], [ "docstr", "AutoDocstring: Current" @@ -8458,35 +6287,37 @@ ], "file_history": [ + "/home/yannic/work/stacosys/stacosys/db/dao.py", + "/home/yannic/work/stacosys/tests/test_db.py", + "/home/yannic/work/stacosys/stacosys/run.py", + "/home/yannic/work/stacosys/config-dev.ini", + "/home/yannic/work/stacosys/dbmigration/migrate_from_3.3_to_4.0.py", + "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", + "/home/yannic/work/stacosys/stacosys/model/comment.py", + "/home/yannic/work/stacosys/tests/test_api.py", + "/home/yannic/work/stacosys/tests/test_form.py", + "/home/yannic/work/stacosys/config.ini", + "/home/yannic/work/stacosys/stacosys/service/configuration.py", + "/home/yannic/work/stacosys/tests/test_config.py", "/home/yannic/work/stacosys/dbmigration/migrate_from_1.1_to_2.0.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/pydal/objects.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/markdown/extensions/def_list.py", "/home/yannic/work/stacosys/stacosys/db/database.py", "/home/yannic/work/stacosys/pyproject.toml", "/home/yannic/work/stacosys/tests/test_rssfeed.py", - "/home/yannic/work/stacosys/tests/test_db.py", - "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", "/home/yannic/work/stacosys/stacosys/service/mail.py", "/home/yannic/work/stacosys/stacosys/interface/form.py", "/home/yannic/work/stacosys/stacosys/interface/__init__.py", "/home/yannic/work/stacosys/stacosys/interface/api.py", - "/home/yannic/work/stacosys/stacosys/db/dao.py", - "/home/yannic/work/stacosys/stacosys/model/comment.py", - "/home/yannic/work/stacosys/stacosys/run.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/peewee.py", - "/home/yannic/work/stacosys/config-dev.ini", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", "/home/yannic/work/stacosys/tests/test_mail.py", "/home/yannic/work/stacosys/Makefile", "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", - "/home/yannic/work/stacosys/tests/test_config.py", "/home/yannic/work/stacosys/.venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi", "/usr/lib64/python3.11/smtplib.py", "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", - "/home/yannic/work/stacosys/stacosys/service/configuration.py", - "/home/yannic/work/stacosys/config.ini", "/home/yannic/work/stacosys/comments.xml", - "/home/yannic/work/stacosys/tests/test_api.py", "/home/yannic/work/stacosys/stacosys/service/__init__.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/PyRSS2Gen.py", "/home/yannic/work/blog/README.md", @@ -8495,7 +6326,6 @@ "/home/yannic/work/stacosys/stacosys/core/__init__.py", "/home/yannic/work/stacosys/stacosys/core/configuration.py", "/home/yannic/work/stacosys/.pylintrc", - "/home/yannic/work/stacosys/tests/test_form.py", "/home/yannic/work/stacosys/stacosys/core/mailer.py", "/home/yannic/work/stacosys/stacosys/conf/config.py", "/home/yannic/work/stacosys/stacosys/core/rss.py", @@ -8524,6 +6354,8 @@ "case_sensitive": false, "find_history": [ + "find_not_published_comments", + "find_comment_by_id", "asdict", "def delete", "db_dal", @@ -8559,19 +6391,18 @@ { "buffer": 0, "file": "stacosys/db/__init__.py", - "selected": true, "semi_transient": false, "settings": { - "buffer_size": 672, + "buffer_size": 701, "regions": { }, "selection": [ [ - 195, - 195 + 148, + 148 ] ], "settings": @@ -8593,7 +6424,7 @@ } ], "lsp_active": true, - "lsp_hover_provider_count": 3, + "lsp_hover_provider_count": 5, "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/__init__.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", @@ -8604,25 +6435,25 @@ "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 0, + "stack_index": 5, "stack_multiselect": false, "type": "text" }, { "buffer": 1, - "file": "stacosys/run.py", + "file": "tests/test_api.py", "semi_transient": false, "settings": { - "buffer_size": 2472, + "buffer_size": 1625, "regions": { }, "selection": [ [ - 120, - 120 + 699, + 699 ] ], "settings": @@ -8644,8 +6475,8 @@ } ], "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/run.py", + "lsp_hover_provider_count": 2, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, @@ -8655,34 +6486,55 @@ "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 6, + "stack_index": 4, "stack_multiselect": false, "type": "text" }, { "buffer": 2, - "file": "config-dev.ini", + "file": "tests/test_db.py", "semi_transient": false, "settings": { - "buffer_size": 657, + "buffer_size": 2740, "regions": { }, "selection": [ [ - 59, - 59 + 2075, + 2075 ] ], "settings": { - "lsp_uri": "file:///home/yannic/work/stacosys/config-dev.ini", - "syntax": "Packages/Text/Plain text.tmLanguage" + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".[", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "LSP-pyright" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 2, + "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_db.py", + "show_definitions": false, + "syntax": "Packages/Python/Python.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 0.0, + "translation.y": 399.0, "zoom_level": 1.0 }, "stack_index": 1, @@ -8691,60 +6543,74 @@ }, { "buffer": 3, - "file": "dbmigration/migrate_from_3.3_to_4.0.py", + "selected": true, "semi_transient": false, "settings": { - "buffer_size": 1022, + "buffer_size": 506, "regions": { }, "selection": [ [ - 369, - 369 + 506, + 506 ] ], "settings": { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/dbmigration/migrate_from_3.3_to_4.0.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true + "auto_indent": false, + "lsp_uri": "buffer://sublime/109", + "syntax": "Packages/Text/Plain text.tmLanguage", + "tab_width": 2, + "word_wrap": true }, "translation.x": 0.0, "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 2, + "stack_index": 0, "stack_multiselect": false, "type": "text" }, { "buffer": 4, - "file": "stacosys/db/dao.py", + "file": "Makefile", "semi_transient": false, "settings": + { + "buffer_size": 338, + "regions": + { + }, + "selection": + [ + [ + 106, + 139 + ] + ], + "settings": + { + "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", + "syntax": "Packages/Makefile/Makefile.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": false + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 3, + "stack_multiselect": false, + "type": "text" + }, + { + "buffer": 5, + "file": "stacosys/db/dao.py", + "semi_transient": true, + "settings": { "buffer_size": 2026, "regions": @@ -8753,8 +6619,8 @@ "selection": [ [ - 80, - 80 + 724, + 751 ] ], "settings": @@ -8784,397 +6650,10 @@ "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 0.0, + "translation.y": 57.0, "zoom_level": 1.0 }, - "stack_index": 7, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 5, - "file": "stacosys/service/rssfeed.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1754, - "regions": - { - }, - "selection": - [ - [ - 1668, - 1631 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/rssfeed.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 456.0, - "zoom_level": 1.0 - }, - "stack_index": 8, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 6, - "file": "stacosys/model/comment.py", - "semi_transient": false, - "settings": - { - "buffer_size": 421, - "regions": - { - }, - "selection": - [ - [ - 421, - 421 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/model/comment.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 11, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 7, - "file": "tests/test_api.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1655, - "regions": - { - }, - "selection": - [ - [ - 935, - 935 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 9, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 8, - "file": "tests/test_db.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1835, - "regions": - { - }, - "selection": - [ - [ - 335, - 335 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_db.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 10, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 9, - "file": "tests/test_form.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1125, - "regions": - { - }, - "selection": - [ - [ - 300, - 300 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_form.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 12, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 10, - "file": "config.ini", - "semi_transient": false, - "settings": - { - "buffer_size": 500, - "regions": - { - }, - "selection": - [ - [ - 66, - 66 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/config.ini", - "syntax": "Packages/Text/Plain text.tmLanguage" - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 5, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 11, - "file": "stacosys/service/configuration.py", - "semi_transient": false, - "settings": - { - "buffer_size": 2563, - "regions": - { - }, - "selection": - [ - [ - 138, - 138 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 3, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/configuration.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 4, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 12, - "file": "tests/test_config.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1113, - "regions": - { - }, - "selection": - [ - [ - 1113, - 1113 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_config.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 171.0, - "zoom_level": 1.0 - }, - "stack_index": 3, + "stack_index": 2, "stack_multiselect": false, "type": "text" } From 18d4225eecf0b7ccc6078a0da41c996108dded2e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 2 Dec 2022 08:59:32 +0100 Subject: [PATCH 493/586] remove dead code --- stacosys/service/configuration.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stacosys/service/configuration.py b/stacosys/service/configuration.py index d21dc87..986409e 100644 --- a/stacosys/service/configuration.py +++ b/stacosys/service/configuration.py @@ -33,11 +33,6 @@ class Config: _cfg = configparser.ConfigParser() - # def __new__(cls): - # if not hasattr(cls, "instance"): - # cls.instance = super(Config, cls).__new__(cls) - # return cls.instance - def load(self, config_pathname): self._cfg.read(config_pathname) From f37a8f797e3ab67949f326dc05829ee0f3caf207 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 2 Dec 2022 08:59:55 +0100 Subject: [PATCH 494/586] improve tests --- tests/test_api.py | 5 +-- tests/test_config.py | 21 +++++++++-- tests/test_db.py | 88 ++++++++++++++++++++++++++++++++++++++----- tests/test_form.py | 3 +- tests/test_mail.py | 10 +++-- tests/test_rssfeed.py | 3 +- 6 files changed, 107 insertions(+), 23 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 0d84a0c..6badcbd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -6,9 +6,8 @@ import logging import pytest -from stacosys.db import database, dao -from stacosys.interface import api -from stacosys.interface import app +from stacosys.db import dao, database +from stacosys.interface import api, app def init_test_db(): diff --git a/tests/test_config.py b/tests/test_config.py index fe44ef2..ae173c2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,29 +6,42 @@ import pytest from stacosys.service import config from stacosys.service.configuration import ConfigParameter -EXPECTED_DB_SQLITE_FILE = "sqlite://db.sqlite" +EXPECTED_DB = "sqlite://db.sqlite" EXPECTED_HTTP_PORT = 8080 EXPECTED_LANG = "fr" @pytest.fixture def init_config(): - config.put(ConfigParameter.DB, EXPECTED_DB_SQLITE_FILE) - config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + config.put(ConfigParameter.DB, EXPECTED_DB) + config.put(ConfigParameter.HTTP_PORT, EXPECTED_HTTP_PORT) + + +def test_split_key(): + section, param = config._split_key(ConfigParameter.HTTP_PORT) + assert section == "http" and param == "port" + def test_exists(init_config): assert config.exists(ConfigParameter.DB) + def test_get(init_config): - assert config.get(ConfigParameter.DB) == EXPECTED_DB_SQLITE_FILE + assert config.get(ConfigParameter.DB) == EXPECTED_DB assert config.get(ConfigParameter.HTTP_HOST) == "" assert config.get(ConfigParameter.HTTP_PORT) == str(EXPECTED_HTTP_PORT) assert config.get_int(ConfigParameter.HTTP_PORT) == EXPECTED_HTTP_PORT with pytest.raises(AssertionError): config.get_bool(ConfigParameter.DB) + def test_put(init_config): assert not config.exists(ConfigParameter.LANG) config.put(ConfigParameter.LANG, EXPECTED_LANG) assert config.exists(ConfigParameter.LANG) assert config.get(ConfigParameter.LANG) == EXPECTED_LANG + + +def test_check(init_config): + success, error = config.check() + assert not success and error diff --git a/tests/test_db.py b/tests/test_db.py index d53133c..ebacd80 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,22 +1,48 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- +import time import pytest -from stacosys.db import dao -from stacosys.db import database +from stacosys.db import dao, database +from stacosys.model.comment import Comment + @pytest.fixture def setup_db(): database.configure("sqlite:memory://db.sqlite") -def test_dao_published(setup_db): +def equals_comment(comment: Comment, other): + return ( + comment.id == other.id + and comment.author_gravatar == other.author_gravatar + and comment.author_name == other.author_name + and comment.author_site == other.author_site + and comment.content == other.content + and comment.created == other.created + and comment.notified == other.notified + and comment.published == other.published + ) - # test count published + +def test_find_comment_by_id(setup_db): + assert dao.find_comment_by_id(1) is None + c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") + assert c1.id is not None + find_c1 = dao.find_comment_by_id(c1.id) + assert find_c1 + assert equals_comment(c1, find_c1) + c1.id = find_c1.id + dao.delete_comment(c1) + assert dao.find_comment_by_id(c1.id) is None + + +def test_dao_published(setup_db): assert 0 == dao.count_published_comments("") c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") assert 0 == dao.count_published_comments("") + assert 1 == len(dao.find_not_published_comments()) dao.publish_comment(c1) assert 1 == dao.count_published_comments("") c2 = dao.create_comment("/post2", "Yax", "", "", "Comment 2") @@ -24,20 +50,19 @@ def test_dao_published(setup_db): assert 2 == dao.count_published_comments("") c3 = dao.create_comment("/post2", "Yax", "", "", "Comment 3") dao.publish_comment(c3) + assert 0 == len(dao.find_not_published_comments()) + + # count published assert 1 == dao.count_published_comments("/post1") assert 2 == dao.count_published_comments("/post2") - # test find published + # find published assert 0 == len(dao.find_published_comments_by_url("/")) assert 1 == len(dao.find_published_comments_by_url("/post1")) assert 2 == len(dao.find_published_comments_by_url("/post2")) - dao.delete_comment(c1) - assert 0 == len(dao.find_published_comments_by_url("/post1")) def test_dao_notified(setup_db): - - # test count notified assert 0 == len(dao.find_not_notified_comments()) c1 = dao.create_comment("/post1", "Yax", "", "", "Comment 1") assert 1 == len(dao.find_not_notified_comments()) @@ -51,3 +76,48 @@ def test_dao_notified(setup_db): dao.notify_comment(c3) assert 0 == len(dao.find_not_notified_comments()) + +def create_comment(url, author_name, content): + return dao.create_comment(url, author_name, "", "", content) + + +def test_find_recent_published_comments(setup_db): + + comments = [] + comments.append(create_comment("/post", "Adam", "Comment 1")) + comments.append(create_comment("/post", "Arf", "Comment 2")) + comments.append(create_comment("/post", "Arwin", "Comment 3")) + comments.append(create_comment("/post", "Bill", "Comment 4")) + comments.append(create_comment("/post", "Bo", "Comment 5")) + comments.append(create_comment("/post", "Charles", "Comment 6")) + comments.append(create_comment("/post", "Dan", "Comment 7")) + comments.append(create_comment("/post", "Dwayne", "Comment 8")) + comments.append(create_comment("/post", "Erl", "Comment 9")) + comments.append(create_comment("/post", "Jay", "Comment 10")) + comments.append(create_comment("/post", "Kenny", "Comment 11")) + comments.append(create_comment("/post", "Lord", "Comment 12")) + + rows = dao.find_recent_published_comments() + assert len(rows) == 0 + + # publish every second + for comment in comments: + dao.publish_comment(comment) + time.sleep(1) + + rows = dao.find_recent_published_comments() + assert len(rows) == 10 + + authors = [row.author_name for row in rows] + assert authors == [ + "Lord", + "Kenny", + "Jay", + "Erl", + "Dwayne", + "Dan", + "Charles", + "Bo", + "Bill", + "Arwin", + ] diff --git a/tests/test_form.py b/tests/test_form.py index 1ddd4ae..9244483 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -6,8 +6,7 @@ import logging import pytest from stacosys.db import database -from stacosys.interface import app -from stacosys.interface import form +from stacosys.interface import app, form @pytest.fixture diff --git a/tests/test_mail.py b/tests/test_mail.py index 192c3cc..9f9597f 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -2,10 +2,12 @@ # -*- coding: UTF-8 -*- import pytest + from stacosys.service import mailer + def test_configure_and_check(): - mailer.configure_smtp("localhost", 2525, "admin", "admin") - mailer.configure_destination("admin@mydomain.com") - with pytest.raises(ConnectionRefusedError): - mailer.check() \ No newline at end of file + mailer.configure_smtp("localhost", 2525, "admin", "admin") + mailer.configure_destination("admin@mydomain.com") + with pytest.raises(ConnectionRefusedError): + mailer.check() diff --git a/tests/test_rssfeed.py b/tests/test_rssfeed.py index 713eead..021fc1e 100644 --- a/tests/test_rssfeed.py +++ b/tests/test_rssfeed.py @@ -3,5 +3,6 @@ from stacosys.service import rss + def test_configure(): - rss.configure("comments.xml", "blog", "http", "blog.mydomain.com") + rss.configure("comments.xml", "blog", "http", "blog.mydomain.com") From 60f2d580765db30d130b4569edb9a30b65ed8232 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:02:24 +0100 Subject: [PATCH 495/586] finalize 3.3 release --- Makefile | 2 +- stacosys.sublime-workspace | 6333 +---------------------------------- stacosys/service/rssfeed.py | 2 - tests/test_db.py | 1 + 4 files changed, 44 insertions(+), 6294 deletions(-) diff --git a/Makefile b/Makefile index 6c11464..e7377fc 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ test: poetry run coverage report typehint: - poetry run mypy --ignore-missing-imports stacosys/ + poetry run mypy --ignore-missing-imports stacosys/ tests/ lint: poetry run pylint stacosys/ diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace index 3bf70a7..d1be777 100644 --- a/stacosys.sublime-workspace +++ b/stacosys.sublime-workspace @@ -3,6 +3,18 @@ { "selected_items": [ + [ + "init", + "init_config" + ], + [ + "author", + "author_name=" + ], + [ + "autho", + "author_name" + ], [ "au", "author_gravatar=" @@ -68,6070 +80,12 @@ "buffers": [ { - "file": "stacosys/db/__init__.py", + "file": "stacosys/service/rssfeed.py", "settings": { - "buffer_size": 701, - "encoding": "UTF-8", + "buffer_size": 1754, "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 1, - 1, - "paste", - null, - "AQAAAAAAAAAAAAAALQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/" - ], - [ - 3, - 1, - "insert", - { - "characters": "\n\nd" - }, - "AwAAAC0AAAAAAAAALgAAAAAAAAAAAAAALgAAAAAAAAAvAAAAAAAAAAAAAAAvAAAAAAAAADAAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 4, - 1, - "insert", - { - "characters": "b" - }, - "AQAAADAAAAAAAAAAMQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMAAAAAAAAAAwAAAAAAAAAAAAAAAAAPC/" - ], - [ - 5, - 1, - "insert", - { - "characters": " =" - }, - "AgAAADEAAAAAAAAAMgAAAAAAAAAAAAAAMgAAAAAAAAAzAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMQAAAAAAAAAxAAAAAAAAAAAAAAAAAPC/" - ], - [ - 6, - 1, - "insert", - { - "characters": " None" - }, - "BQAAADMAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADYAAAAAAAAAAAAAADYAAAAAAAAANwAAAAAAAAAAAAAANwAAAAAAAAA4AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAMwAAAAAAAAAzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 11, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAC4AAAAAAAAALwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 12, - 1, - "paste", - null, - "AQAAAC8AAAAAAAAASwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAALwAAAAAAAAAvAAAAAAAAAAAAAAAAAPC/" - ], - [ - 13, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAEsAAAAAAAAATAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAASwAAAAAAAABLAAAAAAAAAAAAAAAAAPC/" - ], - [ - 15, - 1, - "insert", - { - "characters": "\n\n" - }, - "AgAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVgAAAAAAAABWAAAAAAAAAAAAAAAAAAAA" - ], - [ - 16, - 1, - "paste", - null, - "AQAAAFgAAAAAAAAA6wAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAWAAAAAAAAABYAAAAAAAAAAAAAAAAAPC/" - ], - [ - 21, - 1, - "insert", - { - "characters": "i" - }, - "AgAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABoAAAAAAAAAAEAAABs", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaAAAAAAAAABnAAAAAAAAAAAAAAAAAPC/" - ], - [ - 29, - 1, - "insert", - { - "characters": "db" - }, - "AwAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAA8AAABkYi5pbml0KGRiX3VybClwAAAAAAAAAHEAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbwAAAAAAAAB+AAAAAAAAAAAAAAAAAPC/" - ], - [ - 30, - 1, - "insert", - { - "characters": " " - }, - "AgAAAHEAAAAAAAAAcgAAAAAAAAAAAAAAcgAAAAAAAABzAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcQAAAAAAAABxAAAAAAAAAAAAAAAAAPC/" - ], - [ - 31, - 1, - "left_delete", - null, - "AQAAAHIAAAAAAAAAcgAAAAAAAAABAAAAIA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 32, - 1, - "insert", - { - "characters": "=" - }, - "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcgAAAAAAAAByAAAAAAAAAAAAAAAAAPC/" - ], - [ - 33, - 1, - "insert", - { - "characters": " " - }, - "AQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAcwAAAAAAAABzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "", - null, - "AQAAAHQAAAAAAAAAjgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdAAAAAAAAAB0AAAAAAAAAAAAAAAAAPC/" - ], - [ - 39, - 1, - "insert", - { - "characters": "db_" - }, - "BAAAAHgAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAB5AAAAAAAAABUAAAAnc3FsaXRlOi8vc3RvcmFnZS5kYid5AAAAAAAAAHoAAAAAAAAAAAAAAHoAAAAAAAAAewAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAjQAAAAAAAAB4AAAAAAAAAAAAAAAAAPC/" - ], - [ - 42, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"09.9999.db_uri\",\"kind\":6,\"data\":{\"position\":{\"line\":8,\"character\":16},\"symbolLabel\":\"db_uri\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_uri\"},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "db_uri" - }, - "AgAAAHgAAAAAAAAAeAAAAAAAAAADAAAAZGJfeAAAAAAAAAB+AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" - ], - [ - 46, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 48, - 1, - "trim_trailing_white_space", - null, - "AQAAAIAAAAAAAAAAgAAAAAAAAAAEAAAAICAgIA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" - ], - [ - 55, - 1, - "reindent", - null, - "AQAAAIAAAAAAAAAAhAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" - ], - [ - 56, - 1, - "insert", - { - "characters": "db." - }, - "AwAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAACGAAAAAAAAAIcAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAAPC/" - ], - [ - 57, - 1, - "insert", - { - "characters": "defi" - }, - "BAAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 58, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"09.9999.define_table\",\"kind\":2,\"data\":{\"position\":{\"line\":9,\"character\":11},\"symbolLabel\":\"define_table\",\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"define_table\"},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "define_table" - }, - "AgAAAIcAAAAAAAAAhwAAAAAAAAAEAAAAZGVmaYcAAAAAAAAAkwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" - ], - [ - 59, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAJMAAAAAAAAAlQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAkwAAAAAAAACTAAAAAAAAAAAAAAAAAPC/" - ], - [ - 60, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAJQAAAAAAAAAlgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAlAAAAAAAAACUAAAAAAAAAAAAAAAAAPC/" - ], - [ - 61, - 1, - "insert", - { - "characters": "comment" - }, - "BwAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAACaAAAAAAAAAJsAAAAAAAAAAAAAAJsAAAAAAAAAnAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" - ], - [ - 67, - 1, - "left_delete", - null, - "AQAAAJ8AAAAAAAAAnwAAAAAAAABsAAAAICAgIGRiLmNvbm5lY3QoKQoKICAgIGZyb20gc3RhY29zeXMubW9kZWwuY29tbWVudCBpbXBvcnQgQ29tbWVudAoKICAgIGRiLmNyZWF0ZV90YWJsZXMoW0NvbW1lbnRdLCBzYWZlPVRydWUp", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwEAAAAAAACfAAAAAAAAAAAAAAAAAPC/" - ], - [ - 70, - 1, - "insert", - { - "characters": ";" - }, - "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 71, - 1, - "left_delete", - null, - "AQAAAJ0AAAAAAAAAnQAAAAAAAAABAAAAOw", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAngAAAAAAAACeAAAAAAAAAAAAAAAAAPC/" - ], - [ - 72, - 1, - "insert", - { - "characters": "," - }, - "AQAAAJ0AAAAAAAAAngAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnQAAAAAAAACdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 73, - 1, - "insert", - { - "characters": " " - }, - "AQAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAngAAAAAAAACeAAAAAAAAAAAAAAAAAPC/" - ], - [ - 74, - 1, - "insert", - { - "characters": "Field" - }, - "BQAAAJ8AAAAAAAAAoAAAAAAAAAAAAAAAoAAAAAAAAAChAAAAAAAAAAAAAAChAAAAAAAAAKIAAAAAAAAAAAAAAKIAAAAAAAAAowAAAAAAAAAAAAAAowAAAAAAAACkAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnwAAAAAAAACfAAAAAAAAAAAAAAAAAPC/" - ], - [ - 75, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAKQAAAAAAAAApgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApAAAAAAAAACkAAAAAAAAAAAAAAAAAPC/" - ], - [ - 76, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAKUAAAAAAAAApwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApQAAAAAAAAClAAAAAAAAAAAAAAAAAPC/" - ], - [ - 77, - 1, - "paste", - null, - "AQAAAKYAAAAAAAAAqQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAApgAAAAAAAACmAAAAAAAAAAAAAAAAAPC/" - ], - [ - 81, - 1, - "insert", - { - "characters": "," - }, - "AQAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAqwAAAAAAAACrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 82, - 1, - "insert", - { - "characters": " F" - }, - "AgAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArAAAAAAAAACsAAAAAAAAAAAAAAAAAPC/" - ], - [ - 83, - 1, - "insert", - { - "characters": "ield" - }, - "BAAAAK4AAAAAAAAArwAAAAAAAAAAAAAArwAAAAAAAACwAAAAAAAAAAAAAACwAAAAAAAAALEAAAAAAAAAAAAAALEAAAAAAAAAsgAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAArgAAAAAAAACuAAAAAAAAAAAAAAAAAPC/" - ], - [ - 84, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAALIAAAAAAAAAtAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgAAAAAAAACyAAAAAAAAAAAAAAAAAPC/" - ], - [ - 85, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAALMAAAAAAAAAtQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAswAAAAAAAACzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 86, - 1, - "paste", - null, - "AQAAALQAAAAAAAAAuwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAtAAAAAAAAAC0AAAAAAAAAAAAAAAAAPC/" - ], - [ - 88, - 1, - "insert", - { - "characters": "," - }, - "AQAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvAAAAAAAAAC8AAAAAAAAAAAAAAAAAPC/" - ], - [ - 89, - 1, - "insert", - { - "characters": " type=" - }, - "BgAAAL0AAAAAAAAAvgAAAAAAAAAAAAAAvgAAAAAAAAC/AAAAAAAAAAAAAAC/AAAAAAAAAMAAAAAAAAAAAAAAAMAAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAADCAAAAAAAAAAAAAADCAAAAAAAAAMMAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvQAAAAAAAAC9AAAAAAAAAAAAAAAAAPC/" - ], - [ - 90, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAMMAAAAAAAAAxQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwwAAAAAAAADDAAAAAAAAAAAAAAAAAPC/" - ], - [ - 91, - 1, - "insert", - { - "characters": "date" - }, - "BAAAAMQAAAAAAAAAxQAAAAAAAAAAAAAAxQAAAAAAAADGAAAAAAAAAAAAAADGAAAAAAAAAMcAAAAAAAAAAAAAAMcAAAAAAAAAyAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAxAAAAAAAAADEAAAAAAAAAAAAAAAAAPC/" - ], - [ - 95, - 1, - "insert", - { - "characters": ";" - }, - "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 96, - 1, - "insert", - { - "characters": " " - }, - "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywAAAAAAAADLAAAAAAAAAAAAAAAAAPC/" - ], - [ - 97, - 2, - "left_delete", - null, - "AgAAAMsAAAAAAAAAywAAAAAAAAABAAAAIMoAAAAAAAAAygAAAAAAAAABAAAAOw", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 98, - 1, - "insert", - { - "characters": "," - }, - "AQAAAMoAAAAAAAAAywAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAygAAAAAAAADKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 99, - 1, - "insert", - { - "characters": " " - }, - "AQAAAMsAAAAAAAAAzAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywAAAAAAAADLAAAAAAAAAAAAAAAAAPC/" - ], - [ - 104, - 1, - "paste", - null, - "AQAAAMwAAAAAAAAA6QAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 110, - 1, - "paste", - null, - "AgAAANMAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADbAAAAAAAAAAcAAABjcmVhdGVk", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" - ], - [ - 114, - 1, - "insert", - { - "characters": "," - }, - "AQAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 115, - 1, - "insert", - { - "characters": " de" - }, - "AwAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA6wAAAAAAAADsAAAAAAAAAAAAAADsAAAAAAAAAO0AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAADqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 116, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"sortText\":\"04.9999.default\",\"filterText\":\"default\",\"kind\":6,\"data\":{\"position\":{\"line\":9,\"character\":109},\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"default=\"},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "default=" - }, - "AgAAAOsAAAAAAAAA6wAAAAAAAAACAAAAZGXrAAAAAAAAAPMAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA7QAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 117, - 1, - "insert", - { - "characters": "None" - }, - "BAAAAPMAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD1AAAAAAAAAAAAAAD1AAAAAAAAAPYAAAAAAAAAAAAAAPYAAAAAAAAA9wAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 127, - 1, - "", - null, - "AgAAAPMAAAAAAAAA+gAAAAAAAAAAAAAA+gAAAAAAAAD6AAAAAAAAAAQAAABOb25l", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8wAAAAAAAAD3AAAAAAAAAAAAAAAAAPC/" - ], - [ - 133, - 2, - "left_delete", - null, - "AgAAAOoAAAAAAAAA6gAAAAAAAAARAAAAIGRlZmF1bHQ9REVGQVVMVCnpAAAAAAAAAOkAAAAAAAAAAQAAACw", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" - ], - [ - 135, - 1, - "insert", - { - "characters": "," - }, - "AQAAAOoAAAAAAAAA6wAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6gAAAAAAAADqAAAAAAAAAAAAAAAAAPC/" - ], - [ - 136, - 1, - "insert", - { - "characters": " " - }, - "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 141, - 1, - "paste", - null, - "AQAAAOsAAAAAAAAACQEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 148, - 1, - "paste", - null, - "AgAAAPIAAAAAAAAA+wAAAAAAAAAAAAAA+wAAAAAAAAD7AAAAAAAAAAgAAABub3RpZmllZA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8gAAAAAAAAD6AAAAAAAAAAAAAAAAAPC/" - ], - [ - 153, - 1, - "insert", - { - "characters": "," - }, - "AQAAAAoBAAAAAAAACwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACgEAAAAAAAAKAQAAAAAAAAAAAAAAAPC/" - ], - [ - 154, - 1, - "insert", - { - "characters": " " - }, - "AQAAAAsBAAAAAAAADAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAACwEAAAAAAAALAQAAAAAAAAAAAAAAAPC/" - ], - [ - 155, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAAwBAAAAAAAADgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 156, - 1, - "run_macro_file", - { - "file": "res://Packages/Default/Delete Left Right.sublime-macro" - }, - "AgAAAAwBAAAAAAAADAEAAAAAAAABAAAAIgwBAAAAAAAADAEAAAAAAAABAAAAIg", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADQEAAAAAAAANAQAAAAAAAAAAAAAAAPC/" - ], - [ - 157, - 1, - "insert", - { - "characters": "Field" - }, - "BQAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAADAEAAAAAAAAMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 158, - 1, - "insert", - { - "characters": "-" - }, - "AQAAABEBAAAAAAAAEgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" - ], - [ - 159, - 1, - "left_delete", - null, - "AQAAABEBAAAAAAAAEQEAAAAAAAABAAAALQ", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 160, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABEBAAAAAAAAEwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" - ], - [ - 161, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAABIBAAAAAAAAFAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 166, - 1, - "paste", - null, - "AQAAABMBAAAAAAAAHgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" - ], - [ - 171, - 1, - "insert", - { - "characters": "," - }, - "AQAAACABAAAAAAAAIQEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAPC/" - ], - [ - 172, - 1, - "insert", - { - "characters": " " - }, - "AQAAACEBAAAAAAAAIgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIQEAAAAAAAAhAQAAAAAAAAAAAAAAAPC/" - ], - [ - 173, - 1, - "paste", - null, - "AQAAACIBAAAAAAAANgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAIgEAAAAAAAAiAQAAAAAAAAAAAAAAAPC/" - ], - [ - 176, - 1, - "insert", - { - "characters": "site" - }, - "BQAAADABAAAAAAAAMQEAAAAAAAAAAAAAMQEAAAAAAAAxAQAAAAAAAAQAAABuYW1lMQEAAAAAAAAyAQAAAAAAAAAAAAAyAQAAAAAAADMBAAAAAAAAAAAAADMBAAAAAAAANAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANAEAAAAAAAAwAQAAAAAAAAAAAAAAAPC/" - ], - [ - 178, - 1, - "insert", - { - "characters": ";" - }, - "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 179, - 1, - "left_delete", - null, - "AQAAADYBAAAAAAAANgEAAAAAAAABAAAAOw", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 180, - 1, - "insert", - { - "characters": "," - }, - "AQAAADYBAAAAAAAANwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 181, - 1, - "insert", - { - "characters": " " - }, - "AQAAADcBAAAAAAAAOAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 182, - 1, - "paste", - null, - "AQAAADgBAAAAAAAARwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" - ], - [ - 190, - 1, - "paste", - null, - "AQAAADgBAAAAAAAATAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOAEAAAAAAAA4AQAAAAAAAAAAAAAAAPC/" - ], - [ - 192, - 1, - "cut", - null, - "AQAAAEwBAAAAAAAATAEAAAAAAAAPAAAAYXV0aG9yX2dyYXZhdGFy", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAATAEAAAAAAABbAQAAAAAAAAAAAAAAAPC/" - ], - [ - 197, - 1, - "paste", - null, - "AgAAAD8BAAAAAAAATgEAAAAAAAAAAAAATgEAAAAAAABOAQAAAAAAAAsAAABhdXRob3Jfc2l0ZQ", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAPwEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" - ], - [ - 201, - 1, - "insert", - { - "characters": "," - }, - "AQAAADUBAAAAAAAANgEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANQEAAAAAAAA1AQAAAAAAAAAAAAAAAPC/" - ], - [ - 202, - 1, - "insert", - { - "characters": " ef" - }, - "AwAAADYBAAAAAAAANwEAAAAAAAAAAAAANwEAAAAAAAA4AQAAAAAAAAAAAAA4AQAAAAAAADkBAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANgEAAAAAAAA2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 203, - 2, - "left_delete", - null, - "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAZjcBAAAAAAAANwEAAAAAAAABAAAAZQ", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" - ], - [ - 204, - 1, - "insert", - { - "characters": "default=" - }, - "CAAAADcBAAAAAAAAOAEAAAAAAAAAAAAAOAEAAAAAAAA5AQAAAAAAAAAAAAA5AQAAAAAAADoBAAAAAAAAAAAAADoBAAAAAAAAOwEAAAAAAAAAAAAAOwEAAAAAAAA8AQAAAAAAAAAAAAA8AQAAAAAAAD0BAAAAAAAAAAAAAD0BAAAAAAAAPgEAAAAAAAAAAAAAPgEAAAAAAAA/AQAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAANwEAAAAAAAA3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 205, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAD8BAAAAAAAAQQEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAPwEAAAAAAAA/AQAAAAAAAAAAAAAAAPC/" - ], - [ - 208, - 1, - "trim_trailing_white_space", - null, - "AQAAAFwBAAAAAAAAXAEAAAAAAAABAAAAIA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAQQEAAAAAAABBAQAAAAAAAAAAAAAAAPC/" - ], - [ - 219, - 1, - "insert", - { - "characters": "\ndef" - }, - "BAAAAFcAAAAAAAAAWAAAAAAAAAAAAAAAWAAAAAAAAABZAAAAAAAAAAAAAABZAAAAAAAAAFoAAAAAAAAAAAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAVwAAAAAAAABXAAAAAAAAAAAAAAAAgGZA" - ], - [ - 220, - 1, - "insert", - { - "characters": " _" - }, - "AgAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAWwAAAAAAAABbAAAAAAAAAAAAAAAAAPC/" - ], - [ - 221, - 1, - "insert", - { - "characters": "null" - }, - "BAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 222, - 4, - "left_delete", - null, - "BAAAAGAAAAAAAAAAYAAAAAAAAAABAAAAbF8AAAAAAAAAXwAAAAAAAAABAAAAbF4AAAAAAAAAXgAAAAAAAAABAAAAdV0AAAAAAAAAXQAAAAAAAAABAAAAbg", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAYQAAAAAAAABhAAAAAAAAAAAAAAAAAPC/" - ], - [ - 223, - 1, - "insert", - { - "characters": "empty_s" - }, - "BwAAAF0AAAAAAAAAXgAAAAAAAAAAAAAAXgAAAAAAAABfAAAAAAAAAAAAAABfAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAYQAAAAAAAAAAAAAAYQAAAAAAAABiAAAAAAAAAAAAAABiAAAAAAAAAGMAAAAAAAAAAAAAAGMAAAAAAAAAZAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAXQAAAAAAAABdAAAAAAAAAAAAAAAAAPC/" - ], - [ - 224, - 1, - "insert", - { - "characters": "tring" - }, - "BQAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAABpAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAZAAAAAAAAABkAAAAAAAAAAAAAAAAAPC/" - ], - [ - 225, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAGkAAAAAAAAAawAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAaQAAAAAAAABpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 227, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAGsAAAAAAAAAbAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAawAAAAAAAABrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 228, - 1, - "insert", - { - "characters": "\nreturn" - }, - "CAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAHIAAAAAAAAAcwAAAAAAAAAAAAAAcwAAAAAAAAB0AAAAAAAAAAAAAAB0AAAAAAAAAHUAAAAAAAAAAAAAAHUAAAAAAAAAdgAAAAAAAAAAAAAAdgAAAAAAAAB3AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAbAAAAAAAAABsAAAAAAAAAAAAAAAAAPC/" - ], - [ - 229, - 1, - "insert", - { - "characters": " " - }, - "AQAAAHcAAAAAAAAAeAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdwAAAAAAAAB3AAAAAAAAAAAAAAAAAPC/" - ], - [ - 230, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAHgAAAAAAAAAegAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAeAAAAAAAAAB4AAAAAAAAAAAAAAAAAPC/" - ], - [ - 232, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAHoAAAAAAAAAewAAAAAAAAAAAAAAewAAAAAAAAB/AAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAegAAAAAAAAB6AAAAAAAAAAAAAAAAAPC/" - ], - [ - 234, - 1, - "trim_trailing_white_space", - null, - "AQAAAHsAAAAAAAAAewAAAAAAAAAEAAAAICAgIA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAfwAAAAAAAAB/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 242, - 1, - "insert", - { - "characters": "\n" - }, - "BAAAAPAAAAAAAAAA8QAAAAAAAAAAAAAA8QAAAAAAAAD1AAAAAAAAAAAAAADxAAAAAAAAAPEAAAAAAAAABAAAACAgICDxAAAAAAAAAPkAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAPC/" - ], - [ - 245, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAADkBAAAAAAAAOgEAAAAAAAAAAAAAOgEAAAAAAABCAQAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAOQEAAAAAAAA5AQAAAAAAAAAAAAAAAPC/" - ], - [ - 250, - 1, - "paste", - null, - "AgAAAHUBAAAAAAAAggEAAAAAAAAAAAAAggEAAAAAAACCAQAAAAAAAAIAAAAiIg", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAdQEAAAAAAAB3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 252, - 1, - "trim_trailing_white_space", - null, - "AgAAADgBAAAAAAAAOAEAAAAAAAABAAAAIO8AAAAAAAAA7wAAAAAAAAABAAAAIA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAggEAAAAAAACCAQAAAAAAAAAAAAAAAPC/" - ], - [ - 269, - 1, - "insert", - { - "characters": "," - }, - "AQAAAJoBAAAAAAAAmwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAmgEAAAAAAACaAQAAAAAAAAAAAAAAAPC/" - ], - [ - 270, - 1, - "insert", - { - "characters": " " - }, - "AQAAAJsBAAAAAAAAnAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAmwEAAAAAAACbAQAAAAAAAAAAAAAAAPC/" - ], - [ - 271, - 1, - "paste", - null, - "AQAAAJwBAAAAAAAAsQEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAnAEAAAAAAACcAQAAAAAAAAAAAAAAAPC/" - ], - [ - 273, - 1, - "insert", - { - "characters": "," - }, - "AQAAALIBAAAAAAAAswEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAsgEAAAAAAACyAQAAAAAAAAAAAAAAAPC/" - ], - [ - 274, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAALMBAAAAAAAAtAEAAAAAAAAAAAAAtAEAAAAAAAC8AQAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAswEAAAAAAACzAQAAAAAAAAAAAAAAAPC/" - ], - [ - 275, - 1, - "insert", - { - "characters": "Field" - }, - "BQAAALwBAAAAAAAAvQEAAAAAAAAAAAAAvQEAAAAAAAC+AQAAAAAAAAAAAAC+AQAAAAAAAL8BAAAAAAAAAAAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADBAQAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAvAEAAAAAAAC8AQAAAAAAAAAAAAAAAPC/" - ], - [ - 276, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAMEBAAAAAAAAwwEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" - ], - [ - 277, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAMIBAAAAAAAAxAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwgEAAAAAAADCAQAAAAAAAAAAAAAAAPC/" - ], - [ - 278, - 1, - "insert", - { - "characters": "content" - }, - "BwAAAMMBAAAAAAAAxAEAAAAAAAAAAAAAxAEAAAAAAADFAQAAAAAAAAAAAADFAQAAAAAAAMYBAAAAAAAAAAAAAMYBAAAAAAAAxwEAAAAAAAAAAAAAxwEAAAAAAADIAQAAAAAAAAAAAADIAQAAAAAAAMkBAAAAAAAAAAAAAMkBAAAAAAAAygEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAwwEAAAAAAADDAQAAAAAAAAAAAAAAAPC/" - ], - [ - 280, - 1, - "insert", - { - "characters": "," - }, - "AQAAAMsBAAAAAAAAzAEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAywEAAAAAAADLAQAAAAAAAAAAAAAAAPC/" - ], - [ - 281, - 1, - "insert", - { - "characters": " type\"\"" - }, - "BwAAAMwBAAAAAAAAzQEAAAAAAAAAAAAAzQEAAAAAAADOAQAAAAAAAAAAAADOAQAAAAAAAM8BAAAAAAAAAAAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA0AEAAAAAAADRAQAAAAAAAAAAAADRAQAAAAAAANIBAAAAAAAAAAAAANIBAAAAAAAA0wEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAzAEAAAAAAADMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 283, - 1, - "insert", - { - "characters": "=" - }, - "AQAAANEBAAAAAAAA0gEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0QEAAAAAAADRAQAAAAAAAAAAAAAAAPC/" - ], - [ - 287, - 1, - "insert", - { - "characters": ")" - }, - "AQAAANUBAAAAAAAA1gEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" - ], - [ - 290, - 1, - "insert", - { - "characters": "text" - }, - "BAAAANMBAAAAAAAA1AEAAAAAAAAAAAAA1AEAAAAAAADVAQAAAAAAAAAAAADVAQAAAAAAANYBAAAAAAAAAAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAA0wEAAAAAAADTAQAAAAAAAAAAAAAAAPC/" - ], - [ - 305, - 1, - "insert", - { - "characters": "confiu" - }, - "BwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAUAAABzZXR1cIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" - ], - [ - 306, - 1, - "left_delete", - null, - "AQAAAIUAAAAAAAAAhQAAAAAAAAABAAAAdQ", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" - ], - [ - 307, - 1, - "insert", - { - "characters": "gure" - }, - "BAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAAiQAAAAAAAAAAAAAA", - "BAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 5, - 1, - "left_delete", - null, - "AQAAAG0BAAAAAAAAbQEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAG0BAAAAAAAAhAEAAAAAAAAAAAAAAADwvw" - ], - [ - 8, - 1, - "left_delete", - null, - "AQAAAIcBAAAAAAAAhwEAAAAAAAAXAAAALCBkZWZhdWx0PV9lbXB0eV9zdHJpbmc", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcBAAAAAAAAngEAAAAAAAAAAAAAAADwvw" - ], - [ - 13, - 3, - "left_delete", - null, - "AwAAAFgAAAAAAAAAWAAAAAAAAAAiAAAAZGVmIF9lbXB0eV9zdHJpbmcoKToKICAgIHJldHVybiAiIlcAAAAAAAAAVwAAAAAAAAABAAAAClYAAAAAAAAAVgAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHoAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" - ], - [ - 20, - 1, - "insert", - { - "characters": "time" - }, - "BAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAMwAAAAAAAAAzAAAAAAAAAAAAAAAAADwvw" - ], - [ - 23, - 1, - "insert", - { - "characters": "time" - }, - "BAAAABgBAAAAAAAAGQEAAAAAAAAAAAAAGQEAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAABsBAAAAAAAAHAEAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAABgBAAAAAAAAGAEAAAAAAAAAAAAAACCDQA" - ], - [ - 26, - 1, - "insert", - { - "characters": "time" - }, - "BAAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAADwvw" - ], - [ - 29, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAACZAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkYiA9IE5vbmUKCmRlZiBjb25maWd1cmUoZGJfdXJpKToKICAgIGRiID0gREFMKGRiX3VyaSkKICAgIGRiLmRlZmluZV90YWJsZSgiY29tbWVudCIsIEZpZWxkKCJ1cmwiKSwgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJub3RpZmllZCIsIHR5cGU9ImRhdGV0aW1lIiksRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLCBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgRmllbGQoImNvbnRlbnQiLCB0eXBlPSJ0ZXh0IikpCgAAAAAAAAAA0gEAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAPwAAAAAAAAA/AAAAAAAAAAAAAAAAADwvw" - ], - [ - 36, - 1, - "insert", - { - "characters": "dal" - }, - "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABTAAAAAAAAAAQAAABOb25lUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" - ], - [ - 37, - 3, - "left_delete", - null, - "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" - ], - [ - 38, - 1, - "insert", - { - "characters": "dal" - }, - "AwAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" - ], - [ - 39, - 3, - "left_delete", - null, - "AwAAAFQAAAAAAAAAVAAAAAAAAAABAAAAbFMAAAAAAAAAUwAAAAAAAAABAAAAYVIAAAAAAAAAUgAAAAAAAAABAAAAZA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" - ], - [ - 40, - 1, - "insert", - { - "characters": "DAL)" - }, - "BAAAAFIAAAAAAAAAUwAAAAAAAAAAAAAAUwAAAAAAAABUAAAAAAAAAAAAAABUAAAAAAAAAFUAAAAAAAAAAAAAAFUAAAAAAAAAVgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFIAAAAAAAAAUgAAAAAAAAAAAAAAAADwvw" - ], - [ - 41, - 1, - "left_delete", - null, - "AQAAAFUAAAAAAAAAVQAAAAAAAAABAAAAKQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" - ], - [ - 42, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAFUAAAAAAAAAVwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" - ], - [ - 56, - 1, - "insert", - { - "characters": "\ndb." - }, - "BQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACKAAAAAAAAAAAAAACKAAAAAAAAAIsAAAAAAAAAAAAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAjAAAAAAAAACNAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" - ], - [ - 57, - 1, - "insert", - { - "characters": "c" - }, - "AQAAAI0AAAAAAAAAjgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAADwvw" - ], - [ - 58, - 1, - "insert", - { - "characters": "on" - }, - "AgAAAI4AAAAAAAAAjwAAAAAAAAAAAAAAjwAAAAAAAACQAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI4AAAAAAAAAjgAAAAAAAAAAAAAAAADwvw" - ], - [ - 59, - 2, - "left_delete", - null, - "AgAAAI8AAAAAAAAAjwAAAAAAAAABAAAAbo4AAAAAAAAAjgAAAAAAAAABAAAAbw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJAAAAAAAAAAkAAAAAAAAAAAAAAAAADwvw" - ], - [ - 62, - 1, - "left_delete", - null, - "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" - ], - [ - 63, - 1, - "insert", - { - "characters": "atabase" - }, - "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" - ], - [ - 71, - 1, - "paste", - null, - "AgAAAHsAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACDAAAAAAAAAAIAAABkYg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAfQAAAAAAAAAAAAAAAADwvw" - ], - [ - 75, - 1, - "paste", - null, - "AgAAAJ8AAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACnAAAAAAAAAAIAAABkYg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKEAAAAAAAAAnwAAAAAAAAAAAAAAAADwvw" - ], - [ - 79, - 1, - "insert", - { - "characters": "None" - }, - "BQAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABZAAAAAAAAAAUAAABEQUwoKVkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAXQAAAAAAAAAAAAAAAADwvw" - ], - [ - 83, - 1, - "insert", - { - "characters": "db_dal" - }, - "BwAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABOAAAAAAAAAAgAAABkYXRhYmFzZU4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAATQAAAAAAAAAAAAAAAADwvw" - ], - [ - 94, - 1, - "paste", - null, - "AgAAAHgAAAAAAAAAfgAAAAAAAAAAAAAAfgAAAAAAAAB+AAAAAAAAAAgAAABkYXRhYmFzZQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHgAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" - ], - [ - 98, - 1, - "right_delete", - null, - "AQAAAJEAAAAAAAAAkQAAAAAAAAAEAAAAZGIuYw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAkQAAAAAAAAAAAAAAAABCQA" - ], - [ - 100, - 1, - "left_delete", - null, - "AQAAAIwAAAAAAAAAjAAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0AAAAAAAAAjQAAAAAAAAAAAAAAAAAAAA" - ], - [ - 104, - 1, - "paste", - null, - "AgAAAJUAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACbAAAAAAAAAAgAAABkYXRhYmFzZQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAnQAAAAAAAAAAAAAAAADwvw" - ], - [ - 106, - 1, - "trim_trailing_white_space", - null, - "AQAAAIwAAAAAAAAAjAAAAAAAAAAEAAAAICAgIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJsAAAAAAAAAmwAAAAAAAAAAAAAAAADwvw" - ], - [ - 114, - 1, - "insert", - { - "characters": "\ndb_dal." - }, - "CQAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" - ], - [ - 122, - 1, - "insert", - { - "characters": "DAL" - }, - "BAAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABXAAAAAAAAAAQAAABOb25lVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAWgAAAAAAAAAAAAAAAADwvw" - ], - [ - 123, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAFkAAAAAAAAAWwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" - ], - [ - 130, - 1, - "left_delete", - null, - "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" - ], - [ - 131, - 1, - "insert", - { - "characters": "." - }, - "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 135, - 1, - "right_delete", - null, - "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 136, - 1, - "insert", - { - "characters": "." - }, - "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 142, - 1, - "insert", - { - "characters": "#" - }, - "AQAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" - ], - [ - 152, - 1, - "left_delete", - null, - "AQAAAH8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" - ], - [ - 153, - 1, - "insert", - { - "characters": "." - }, - "AQAAAH8AAAAAAAAAgAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 154, - 1, - "insert", - { - "characters": "set" - }, - "AwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" - ], - [ - 155, - 3, - "left_delete", - null, - "AwAAAIIAAAAAAAAAggAAAAAAAAABAAAAdIEAAAAAAAAAgQAAAAAAAAABAAAAZYAAAAAAAAAAgAAAAAAAAAABAAAAcw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" - ], - [ - 156, - 1, - "insert", - { - "characters": "ur" - }, - "AgAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAADwvw" - ], - [ - 157, - 3, - "left_delete", - null, - "AwAAAIEAAAAAAAAAgQAAAAAAAAABAAAAcoAAAAAAAAAAgAAAAAAAAAABAAAAdX8AAAAAAAAAfwAAAAAAAAABAAAALg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIAAAAAAAAAggAAAAAAAAAAAAAAAADwvw" - ], - [ - 158, - 1, - "insert", - { - "characters": ".co" - }, - "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 159, - 1, - "insert", - { - "characters": "n" - }, - "AQAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIIAAAAAAAAAggAAAAAAAAAAAAAAAADwvw" - ], - [ - 171, - 1, - "insert", - { - "characters": "None" - }, - "BAAAAFoAAAAAAAAAWwAAAAAAAAAAAAAAWwAAAAAAAABcAAAAAAAAAAAAAABcAAAAAAAAAF0AAAAAAAAAAAAAAF0AAAAAAAAAXgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoAAAAAAAAAWgAAAAAAAAAAAAAAAADwvw" - ], - [ - 181, - 1, - "left_delete", - null, - "AQAAAFoAAAAAAAAAWgAAAAAAAAAEAAAATm9uZQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoAAAAAAAAAXgAAAAAAAAAAAAAAAADwvw" - ], - [ - 189, - 1, - "insert", - { - "characters": "global" - }, - "BwAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB6AAAAAAAAAAoAAABkYl9kYWwuY29uegAAAAAAAAB7AAAAAAAAAAAAAAB7AAAAAAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAeQAAAAAAAAAAAAAAAADwvw" - ], - [ - 190, - 1, - "insert", - { - "characters": " db_dal" - }, - "BwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAAgwAAAAAAAACEAAAAAAAAAAAAAACEAAAAAAAAAIUAAAAAAAAAAAAAAIUAAAAAAAAAhgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 193, - 1, - "right_delete", - null, - "AQAAAIsAAAAAAAAAiwAAAAAAAAABAAAAIw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsAAAAAAAAAiwAAAAAAAAAAAAAAAABCQA" - ], - [ - 203, - 3, - "right_delete", - null, - "AwAAAE0AAAAAAAAATQAAAAAAAAAPAAAAZGJfZGFsID0gREFMKCkKTQAAAAAAAABNAAAAAAAAAAEAAAAKTQAAAAAAAABNAAAAAAAAAAEAAAAK", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFwAAAAAAAAATQAAAAAAAAAAAAAAAADwvw" - ], - [ - 206, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAADgAQAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpkZWYgY29uZmlndXJlKGRiX3VyaSk6CiAgICBnbG9iYWwgZGJfZGFsCiAgICBkYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAiY29tbWVudCIsCiAgICAgICAgRmllbGQoInVybCIpLAogICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJwdWJsaXNoZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgIEZpZWxkKCJhdXRob3JfZ3JhdmF0YXIiKSwKICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICkKAAAAAAAAAADhAQAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" - ], - [ - 214, - 1, - "insert", - { - "characters": "db" - }, - "AgAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAABPAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" - ], - [ - 215, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAE8AAAAAAAAAUAAAAAAAAAAAAAAAUAAAAAAAAABRAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" - ], - [ - 216, - 3, - "left_delete", - null, - "AwAAAFAAAAAAAAAAUAAAAAAAAAABAAAAPU8AAAAAAAAATwAAAAAAAAABAAAAIE4AAAAAAAAATgAAAAAAAAABAAAAYg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFEAAAAAAAAAUQAAAAAAAAAAAAAAAADwvw" - ], - [ - 217, - 1, - "insert", - { - "characters": "a" - }, - "AQAAAE4AAAAAAAAATwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" - ], - [ - 218, - 1, - "left_delete", - null, - "AQAAAE4AAAAAAAAATgAAAAAAAAABAAAAYQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE8AAAAAAAAATwAAAAAAAAAAAAAAAADwvw" - ], - [ - 219, - 1, - "insert", - { - "characters": "atabase" - }, - "BwAAAE4AAAAAAAAATwAAAAAAAAAAAAAATwAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAUgAAAAAAAAAAAAAAUgAAAAAAAABTAAAAAAAAAAAAAABTAAAAAAAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE4AAAAAAAAATgAAAAAAAAAAAAAAAADwvw" - ], - [ - 220, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFUAAAAAAAAAVQAAAAAAAAAAAAAAAADwvw" - ], - [ - 221, - 1, - "insert", - { - "characters": " " - }, - "AQAAAFcAAAAAAAAAWAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFcAAAAAAAAAVwAAAAAAAAAAAAAAAADwvw" - ], - [ - 223, - 1, - "insert", - { - "characters": "Databas" - }, - "BwAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAFsAAAAAAAAAXAAAAAAAAAAAAAAAXAAAAAAAAABdAAAAAAAAAAAAAABdAAAAAAAAAF4AAAAAAAAAAAAAAF4AAAAAAAAAXwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFgAAAAAAAAAWAAAAAAAAAAAAAAAAADwvw" - ], - [ - 224, - 1, - "insert", - { - "characters": "e" - }, - "AQAAAF8AAAAAAAAAYAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8AAAAAAAAAXwAAAAAAAAAAAAAAAADwvw" - ], - [ - 225, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAGAAAAAAAAAAYgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGAAAAAAAAAAYAAAAAAAAAAAAAAAAADwvw" - ], - [ - 227, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAGIAAAAAAAAAYwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGIAAAAAAAAAYgAAAAAAAAAAAAAAAADwvw" - ], - [ - 230, - 1, - "insert", - { - "characters": "\nclass" - }, - "BgAAAGMAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAABlAAAAAAAAAAAAAABlAAAAAAAAAGYAAAAAAAAAAAAAAGYAAAAAAAAAZwAAAAAAAAAAAAAAZwAAAAAAAABoAAAAAAAAAAAAAABoAAAAAAAAAGkAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGMAAAAAAAAAYwAAAAAAAAAAAAAAAAAAAA" - ], - [ - 231, - 1, - "insert", - { - "characters": " Database" - }, - "CQAAAGkAAAAAAAAAagAAAAAAAAAAAAAAagAAAAAAAABrAAAAAAAAAAAAAABrAAAAAAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAG8AAAAAAAAAcAAAAAAAAAAAAAAAcAAAAAAAAABxAAAAAAAAAAAAAABxAAAAAAAAAHIAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGkAAAAAAAAAaQAAAAAAAAAAAAAAAADwvw" - ], - [ - 232, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAHIAAAAAAAAAdAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAcgAAAAAAAAAAAAAAAADwvw" - ], - [ - 234, - 2, - "left_delete", - null, - "AgAAAHMAAAAAAAAAcwAAAAAAAAABAAAAKXIAAAAAAAAAcgAAAAAAAAABAAAAKA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHQAAAAAAAAAdAAAAAAAAAAAAAAAAADwvw" - ], - [ - 235, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAHIAAAAAAAAAcwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHIAAAAAAAAAcgAAAAAAAAAAAAAAAADwvw" - ], - [ - 236, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" - ], - [ - 243, - 1, - "indent", - null, - "DgAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAlAAAAAAAAACYAAAAAAAAAAAAAACqAAAAAAAAAK4AAAAAAAAAAAAAAMcAAAAAAAAAywAAAAAAAAAAAAAA5AAAAAAAAADoAAAAAAAAAAAAAAD7AAAAAAAAAP8AAAAAAAAAAAAAABUBAAAAAAAAGQEAAAAAAAAAAAAARAEAAAAAAABIAQAAAAAAAAAAAAB0AQAAAAAAAHgBAAAAAAAAAAAAAKUBAAAAAAAAqQEAAAAAAAAAAAAAxwEAAAAAAADLAQAAAAAAAAAAAADpAQAAAAAAAO0BAAAAAAAAAAAAAA8CAAAAAAAAEwIAAAAAAAAAAAAAOgIAAAAAAAA+AgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHkAAAAAAAAADAIAAAAAAAAAAAAAAAAAAA" - ], - [ - 250, - 1, - "insert", - { - "characters": "self," - }, - "BgAAAIsAAAAAAAAAjAAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsAAAAAAAAAiwAAAAAAAAAAAAAAAADwvw" - ], - [ - 254, - 1, - "insert", - { - "characters": "\n\ndef" - }, - "CAAAAHMAAAAAAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAfQAAAAAAAAAAAAAAdAAAAAAAAAB0AAAAAAAAAAQAAAAgICAgeQAAAAAAAAB6AAAAAAAAAAAAAAB6AAAAAAAAAHsAAAAAAAAAAAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHMAAAAAAAAAcwAAAAAAAAAAAAAAAADwvw" - ], - [ - 255, - 1, - "insert", - { - "characters": " __init" - }, - "BwAAAHwAAAAAAAAAfQAAAAAAAAAAAAAAfQAAAAAAAAB+AAAAAAAAAAAAAAB+AAAAAAAAAH8AAAAAAAAAAAAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACBAAAAAAAAAAAAAACBAAAAAAAAAIIAAAAAAAAAAAAAAIIAAAAAAAAAgwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHwAAAAAAAAAfAAAAAAAAAAAAAAAAADwvw" - ], - [ - 256, - 1, - "insert", - { - "characters": "__" - }, - "AgAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIMAAAAAAAAAgwAAAAAAAAAAAAAAAADwvw" - ], - [ - 257, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAIUAAAAAAAAAhwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIUAAAAAAAAAhQAAAAAAAAAAAAAAAADwvw" - ], - [ - 259, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcAAAAAAAAAhwAAAAAAAAAAAAAAAADwvw" - ], - [ - 260, - 1, - "insert", - { - "characters": "\n" - }, - "AwAAAIgAAAAAAAAAiQAAAAAAAAAAAAAAiQAAAAAAAACNAAAAAAAAAAAAAACNAAAAAAAAAJEAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIgAAAAAAAAAiAAAAAAAAAAAAAAAAADwvw" - ], - [ - 261, - 1, - "insert", - { - "characters": "slef" - }, - "BAAAAJEAAAAAAAAAkgAAAAAAAAAAAAAAkgAAAAAAAACTAAAAAAAAAAAAAACTAAAAAAAAAJQAAAAAAAAAAAAAAJQAAAAAAAAAlQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJEAAAAAAAAAkQAAAAAAAAAAAAAAAADwvw" - ], - [ - 262, - 3, - "left_delete", - null, - "AwAAAJQAAAAAAAAAlAAAAAAAAAABAAAAZpMAAAAAAAAAkwAAAAAAAAABAAAAZZIAAAAAAAAAkgAAAAAAAAABAAAAbA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJUAAAAAAAAAlQAAAAAAAAAAAAAAAADwvw" - ], - [ - 263, - 1, - "insert", - { - "characters": "elf" - }, - "AwAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJIAAAAAAAAAkgAAAAAAAAAAAAAAAADwvw" - ], - [ - 267, - 1, - "right_delete", - null, - "AQAAAHUAAAAAAAAAdQAAAAAAAAAhAAAAICAgIGRlZiBfX2luaXRfXygpOgogICAgICAgIHNlbGYK", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHUAAAAAAAAAlgAAAAAAAAAAAAAAAAAAAA" - ], - [ - 270, - 1, - "reindent", - null, - "AQAAAHUAAAAAAAAAeQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHUAAAAAAAAAdQAAAAAAAAAAAAAAAAAAAA" - ], - [ - 271, - 1, - "insert", - { - "characters": "db" - }, - "AgAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHkAAAAAAAAAeQAAAAAAAAAAAAAAAADwvw" - ], - [ - 272, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAewAAAAAAAAAAAAAAAADwvw" - ], - [ - 273, - 2, - "left_delete", - null, - "AgAAAHwAAAAAAAAAfAAAAAAAAAABAAAAPXsAAAAAAAAAewAAAAAAAAABAAAAIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH0AAAAAAAAAfQAAAAAAAAAAAAAAAADwvw" - ], - [ - 274, - 1, - "insert", - { - "characters": "_dal" - }, - "BAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAHsAAAAAAAAAewAAAAAAAAAAAAAAAADwvw" - ], - [ - 275, - 1, - "left_delete", - null, - "AQAAAH4AAAAAAAAAfgAAAAAAAAABAAAAbA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 276, - 1, - "insert", - { - "characters": "l" - }, - "AQAAAH4AAAAAAAAAfwAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4AAAAAAAAAfgAAAAAAAAAAAAAAAADwvw" - ], - [ - 277, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"09.9999.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":9,\"character\":10},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "db_dal" - }, - "AgAAAHkAAAAAAAAAeQAAAAAAAAAGAAAAZGJfZGFseQAAAAAAAAB/AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 278, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAACEAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 281, - 1, - "insert", - { - "characters": " =" - }, - "AwAAAH8AAAAAAAAAgAAAAAAAAAAAAAAAgQAAAAAAAACBAAAAAAAAAAQAAAAgICAggAAAAAAAAACBAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAADwvw" - ], - [ - 282, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAACDAAAAAAAAAIQAAAAAAAAAAAAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIEAAAAAAAAAgQAAAAAAAAAAAAAAAADwvw" - ], - [ - 291, - 1, - "right_delete", - null, - "AQAAALAAAAAAAAAAsAAAAAAAAAANAAAAZ2xvYmFsIGRiX2RhbA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALAAAAAAAAAAvQAAAAAAAAD////////vfw" - ], - [ - 293, - 1, - "left_delete", - null, - "AQAAAKcAAAAAAAAApwAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAKgAAAAAAAAAqAAAAAAAAAAAAAAAAAAAAA" - ], - [ - 296, - 1, - "insert", - { - "characters": "sl" - }, - "AgAAALgAAAAAAAAAuQAAAAAAAAAAAAAAuQAAAAAAAAC6AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALgAAAAAAAAAuAAAAAAAAAAAAAAAAABSQA" - ], - [ - 297, - 1, - "left_delete", - null, - "AQAAALkAAAAAAAAAuQAAAAAAAAABAAAAbA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALoAAAAAAAAAugAAAAAAAAAAAAAAAADwvw" - ], - [ - 298, - 1, - "insert", - { - "characters": "elf." - }, - "BAAAALkAAAAAAAAAugAAAAAAAAAAAAAAugAAAAAAAAC7AAAAAAAAAAAAAAC7AAAAAAAAALwAAAAAAAAAAAAAALwAAAAAAAAAvQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALkAAAAAAAAAuQAAAAAAAAAAAAAAAADwvw" - ], - [ - 301, - 1, - "insert", - { - "characters": "self." - }, - "BQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAANoAAAAAAAAA2gAAAAAAAAAAAAAAAADwvw" - ], - [ - 303, - 1, - "trim_trailing_white_space", - null, - "AQAAAKcAAAAAAAAApwAAAAAAAAAIAAAAICAgICAgICA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAN8AAAAAAAAA3wAAAAAAAAAAAAAAAADwvw" - ], - [ - 309, - 1, - "cut", - null, - "AQAAAE0AAAAAAAAATQAAAAAAAAAVAAAAZGF0YWJhc2UgPSBEYXRhYmFzZSgp", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGIAAAAAAAAATQAAAAAAAAAAAAAAAAAAAA" - ], - [ - 310, - 2, - "right_delete", - null, - "AgAAAE0AAAAAAAAATQAAAAAAAAABAAAACk0AAAAAAAAATQAAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAE0AAAAAAAAATQAAAAAAAAAAAAAAAADwvw" - ], - [ - 316, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAADUCAAAAAAAANgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUCAAAAAAAANQIAAAAAAAAAAAAAAAAAAA" - ], - [ - 317, - 1, - "paste", - null, - "AQAAADYCAAAAAAAASwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADYCAAAAAAAANgIAAAAAAAAAAAAAAADwvw" - ], - [ - 321, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAABLAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgpjbGFzcyBEYXRhYmFzZToKCiAgICBkYl9kYWwgPSBOb25lCgogICAgZGVmIGNvbmZpZ3VyZShzZWxmLGRiX3VyaSk6CiAgICAgICAgc2VsZi5kYl9kYWwgPSBEQUwoZGJfdXJpKQogICAgICAgIHNlbGYuZGJfZGFsLmRlZmluZV90YWJsZSgKICAgICAgICAgICAgImNvbW1lbnQiLAogICAgICAgICAgICBGaWVsZCgidXJsIiksCiAgICAgICAgICAgIEZpZWxkKCJjcmVhdGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoIm5vdGlmaWVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoInB1Ymxpc2hlZCIsIHR5cGU9ImRhdGV0aW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3JfbmFtZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX3NpdGUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9ncmF2YXRhciIpLAogICAgICAgICAgICBGaWVsZCgiY29udGVudCIsIHR5cGU9InRleHQiKSwKICAgICAgICApCgpkYXRhYmFzZSA9IERhdGFiYXNlKCkAAAAAAAAAAE8CAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADUCAAAAAAAANQIAAAAAAAAAAAAAAKBnQA" - ], - [ - 336, - 1, - "insert", - { - "characters": "Dal" - }, - "BAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAgAAABEYXRhYmFzZVUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAXAAAAAAAAAAAAAAAAADwvw" - ], - [ - 340, - 1, - "insert", - { - "characters": "Db" - }, - "AwAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAMAAABEYWxVAAAAAAAAAFYAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFcAAAAAAAAAVAAAAAAAAAAAAAAAAADwvw" - ], - [ - 341, - 2, - "left_delete", - null, - "AgAAAFUAAAAAAAAAVQAAAAAAAAABAAAAYlQAAAAAAAAAVAAAAAAAAAABAAAARA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" - ], - [ - 342, - 1, - "insert", - { - "characters": "Db" - }, - "AgAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABWAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAVAAAAAAAAAAAAAAAAADwvw" - ], - [ - 343, - 1, - "insert", - { - "characters": "Dal" - }, - "AwAAAFYAAAAAAAAAVwAAAAAAAAAAAAAAVwAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAFkAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFYAAAAAAAAAVgAAAAAAAAAAAAAAAADwvw" - ], - [ - 355, - 1, - "paste", - null, - "AgAAAEECAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABGAgAAAAAAAAgAAABEYXRhYmFzZQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAASQIAAAAAAAAAAAAAAADwvw" - ], - [ - 369, - 1, - "insert", - { - "characters": "Databse" - }, - "CAAAAFQAAAAAAAAAVQAAAAAAAAAAAAAAVQAAAAAAAABVAAAAAAAAAAUAAABEYkRhbFUAAAAAAAAAVgAAAAAAAAAAAAAAVgAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAFgAAAAAAAAAAAAAAFgAAAAAAAAAWQAAAAAAAAAAAAAAWQAAAAAAAABaAAAAAAAAAAAAAABaAAAAAAAAAFsAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFQAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" - ], - [ - 370, - 2, - "left_delete", - null, - "AgAAAFoAAAAAAAAAWgAAAAAAAAABAAAAZVkAAAAAAAAAWQAAAAAAAAABAAAAcw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFsAAAAAAAAAWwAAAAAAAAAAAAAAAADwvw" - ], - [ - 371, - 1, - "insert", - { - "characters": "ase" - }, - "AwAAAFkAAAAAAAAAWgAAAAAAAAAAAAAAWgAAAAAAAABbAAAAAAAAAAAAAABbAAAAAAAAAFwAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkAAAAAAAAAWQAAAAAAAAAAAAAAAADwvw" - ], - [ - 381, - 1, - "paste", - null, - "AgAAAEQCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABMAgAAAAAAAAUAAABEYkRhbA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAASQIAAAAAAAAAAAAAAADwvw" - ], - [ - 386, - 1, - "insert", - { - "characters": "db" - }, - "AwAAADkCAAAAAAAAOgIAAAAAAAAAAAAAOgIAAAAAAAA6AgAAAAAAAAgAAABkYXRhYmFzZToCAAAAAAAAOwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAOQIAAAAAAAAAAAAAAADwvw" - ], - [ - 401, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAADcCAAAAAAAAOAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADcCAAAAAAAANwIAAAAAAAAAAAAAAADwvw" - ], - [ - 402, - 1, - "reindent", - null, - "AQAAADgCAAAAAAAAQAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADgCAAAAAAAAOAIAAAAAAAAAAAAAAADwvw" - ], - [ - 403, - 1, - "left_delete", - null, - "AQAAADwCAAAAAAAAPAIAAAAAAAAEAAAAICAgIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" - ], - [ - 404, - 1, - "insert", - { - "characters": "def" - }, - "AwAAADwCAAAAAAAAPQIAAAAAAAAAAAAAPQIAAAAAAAA+AgAAAAAAAAAAAAA+AgAAAAAAAD8CAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADwCAAAAAAAAPAIAAAAAAAAAAAAAAADwvw" - ], - [ - 405, - 1, - "insert", - { - "characters": " " - }, - "AQAAAD8CAAAAAAAAQAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAD8CAAAAAAAAPwIAAAAAAAAAAAAAAADwvw" - ], - [ - 406, - 1, - "insert", - { - "characters": "dal" - }, - "AwAAAEACAAAAAAAAQQIAAAAAAAAAAAAAQQIAAAAAAABCAgAAAAAAAAAAAABCAgAAAAAAAEMCAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" - ], - [ - 407, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAEMCAAAAAAAARQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEMCAAAAAAAAQwIAAAAAAAAAAAAAAADwvw" - ], - [ - 410, - 1, - "insert", - { - "characters": "self" - }, - "BAAAAEQCAAAAAAAARQIAAAAAAAAAAAAARQIAAAAAAABGAgAAAAAAAAAAAABGAgAAAAAAAEcCAAAAAAAAAAAAAEcCAAAAAAAASAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAARAIAAAAAAAAAAAAAAADwvw" - ], - [ - 412, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAEkCAAAAAAAASgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEkCAAAAAAAASQIAAAAAAAAAAAAAAADwvw" - ], - [ - 413, - 1, - "insert", - { - "characters": "\nreturn" - }, - "CQAAAEoCAAAAAAAASwIAAAAAAAAAAAAASwIAAAAAAABPAgAAAAAAAAAAAABPAgAAAAAAAFMCAAAAAAAAAAAAAFMCAAAAAAAAVAIAAAAAAAAAAAAAVAIAAAAAAABVAgAAAAAAAAAAAABVAgAAAAAAAFYCAAAAAAAAAAAAAFYCAAAAAAAAVwIAAAAAAAAAAAAAVwIAAAAAAABYAgAAAAAAAAAAAABYAgAAAAAAAFkCAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoCAAAAAAAASgIAAAAAAAAAAAAAAADwvw" - ], - [ - 414, - 1, - "insert", - { - "characters": " self." - }, - "BgAAAFkCAAAAAAAAWgIAAAAAAAAAAAAAWgIAAAAAAABbAgAAAAAAAAAAAABbAgAAAAAAAFwCAAAAAAAAAAAAAFwCAAAAAAAAXQIAAAAAAAAAAAAAXQIAAAAAAABeAgAAAAAAAAAAAABeAgAAAAAAAF8CAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFkCAAAAAAAAWQIAAAAAAAAAAAAAAADwvw" - ], - [ - 415, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0000.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":25,\"character\":20},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "db_dal" - }, - "AQAAAF8CAAAAAAAAZQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8CAAAAAAAAXwIAAAAAAAAAAAAAAADwvw" - ], - [ - 420, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAAB3AgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKZGIgPSBEYXRhYmFzZSgpCgAAAAAAAAAAeAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGYCAAAAAAAAZgIAAAAAAAAAAAAAAEBtQA" - ], - [ - 437, - 1, - "insert", - { - "characters": "database" - }, - "CQAAAGgCAAAAAAAAaQIAAAAAAAAAAAAAaQIAAAAAAABpAgAAAAAAAAIAAABkYmkCAAAAAAAAagIAAAAAAAAAAAAAagIAAAAAAABrAgAAAAAAAAAAAABrAgAAAAAAAGwCAAAAAAAAAAAAAGwCAAAAAAAAbQIAAAAAAAAAAAAAbQIAAAAAAABuAgAAAAAAAAAAAABuAgAAAAAAAG8CAAAAAAAAAAAAAG8CAAAAAAAAcAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGgCAAAAAAAAagIAAAAAAAAAAAAAAADwvw" - ], - [ - 439, - 1, - "insert", - { - "characters": "db" - }, - "AgAAAH4CAAAAAAAAfwIAAAAAAAAAAAAAfwIAAAAAAACAAgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4CAAAAAAAAfgIAAAAAAAAAAAAAAABSQA" - ], - [ - 440, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" - ], - [ - 441, - 1, - "insert", - { - "characters": " data" - }, - "BQAAAIICAAAAAAAAgwIAAAAAAAAAAAAAgwIAAAAAAACEAgAAAAAAAAAAAACEAgAAAAAAAIUCAAAAAAAAAAAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACHAgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIICAAAAAAAAggIAAAAAAAAAAAAAAADwvw" - ], - [ - 442, - 1, - "insert", - { - "characters": "base" - }, - "BAAAAIcCAAAAAAAAiAIAAAAAAAAAAAAAiAIAAAAAAACJAgAAAAAAAAAAAACJAgAAAAAAAIoCAAAAAAAAAAAAAIoCAAAAAAAAiwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIcCAAAAAAAAhwIAAAAAAAAAAAAAAADwvw" - ], - [ - 443, - 1, - "insert", - { - "characters": "." - }, - "AQAAAIsCAAAAAAAAjAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIsCAAAAAAAAiwIAAAAAAAAAAAAAAADwvw" - ], - [ - 444, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0000.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":29,\"character\":14},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "db_dal" - }, - "AQAAAIwCAAAAAAAAkgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIwCAAAAAAAAjAIAAAAAAAAAAAAAAADwvw" - ], - [ - 460, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAACSAgAAIyEvdXNyL2Jpbi9lbnYgcHl0aG9uCiMgLSotIGNvZGluZzogdXRmLTggLSotCgpmcm9tIHB5ZGFsIGltcG9ydCBEQUwsIEZpZWxkCgoKY2xhc3MgRGF0YWJhc2U6CgogICAgZGJfZGFsID0gTm9uZQoKICAgIGRlZiBjb25maWd1cmUoc2VsZiwgZGJfdXJpKToKICAgICAgICBzZWxmLmRiX2RhbCA9IERBTChkYl91cmkpCiAgICAgICAgc2VsZi5kYl9kYWwuZGVmaW5lX3RhYmxlKAogICAgICAgICAgICAiY29tbWVudCIsCiAgICAgICAgICAgIEZpZWxkKCJ1cmwiKSwKICAgICAgICAgICAgRmllbGQoImNyZWF0ZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgibm90aWZpZWQiLCB0eXBlPSJkYXRldGltZSIpLAogICAgICAgICAgICBGaWVsZCgicHVibGlzaGVkIiwgdHlwZT0iZGF0ZXRpbWUiKSwKICAgICAgICAgICAgRmllbGQoImF1dGhvcl9uYW1lIiksCiAgICAgICAgICAgIEZpZWxkKCJhdXRob3Jfc2l0ZSIpLAogICAgICAgICAgICBGaWVsZCgiYXV0aG9yX2dyYXZhdGFyIiksCiAgICAgICAgICAgIEZpZWxkKCJjb250ZW50IiwgdHlwZT0idGV4dCIpLAogICAgICAgICkKCiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbAoKCmRhdGFiYXNlID0gRGF0YWJhc2UoKQpkYiA9IGRhdGFiYXNlLmRiX2RhbAAAAAAAAAAAkwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJICAAAAAAAAkgIAAAAAAAAAAAAAAADwvw" - ], - [ - 469, - 1, - "insert", - { - "characters": "DAL" - }, - "BAAAAGwAAAAAAAAAbQAAAAAAAAAAAAAAbQAAAAAAAABtAAAAAAAAAAQAAABOb25lbQAAAAAAAABuAAAAAAAAAAAAAABuAAAAAAAAAG8AAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGwAAAAAAAAAcAAAAAAAAAAAAAAAAADwvw" - ], - [ - 470, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAG8AAAAAAAAAcQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAG8AAAAAAAAAbwAAAAAAAAAAAAAAAADwvw" - ], - [ - 482, - 2, - "left_delete", - null, - "AgAAADgCAAAAAAAAOAIAAAAAAAAuAAAACiAgICBkZWYgZGFsKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmRiX2RhbDcCAAAAAAAANwIAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGYCAAAAAAAAOAIAAAAAAAAAAAAAAADwvw" - ], - [ - 503, - 1, - "insert", - { - "characters": "\n\n" - }, - "BQAAADcCAAAAAAAAOAIAAAAAAAAAAAAAOAIAAAAAAABAAgAAAAAAAAAAAABAAgAAAAAAAEECAAAAAAAAAAAAAEECAAAAAAAASQIAAAAAAAAAAAAAOAIAAAAAAAA4AgAAAAAAAAgAAAAgICAgICAgIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAADcCAAAAAAAANwIAAAAAAAAAAAAAAADwvw" - ], - [ - 504, - 1, - "left_delete", - null, - "AQAAAD0CAAAAAAAAPQIAAAAAAAAEAAAAICAgIA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAQQIAAAAAAAAAAAAAAADwvw" - ], - [ - 505, - 1, - "insert", - { - "characters": "def" - }, - "AwAAAD0CAAAAAAAAPgIAAAAAAAAAAAAAPgIAAAAAAAA/AgAAAAAAAAAAAAA/AgAAAAAAAEACAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAD0CAAAAAAAAPQIAAAAAAAAAAAAAAADwvw" - ], - [ - 506, - 1, - "insert", - { - "characters": " " - }, - "AQAAAEACAAAAAAAAQQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEACAAAAAAAAQAIAAAAAAAAAAAAAAADwvw" - ], - [ - 507, - 1, - "insert", - { - "characters": "get" - }, - "AwAAAEECAAAAAAAAQgIAAAAAAAAAAAAAQgIAAAAAAABDAgAAAAAAAAAAAABDAgAAAAAAAEQCAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEECAAAAAAAAQQIAAAAAAAAAAAAAAADwvw" - ], - [ - 508, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAEQCAAAAAAAARgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEQCAAAAAAAARAIAAAAAAAAAAAAAAADwvw" - ], - [ - 509, - 1, - "insert", - { - "characters": "self" - }, - "BAAAAEUCAAAAAAAARgIAAAAAAAAAAAAARgIAAAAAAABHAgAAAAAAAAAAAABHAgAAAAAAAEgCAAAAAAAAAAAAAEgCAAAAAAAASQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEUCAAAAAAAARQIAAAAAAAAAAAAAAADwvw" - ], - [ - 511, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAEoCAAAAAAAASwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEoCAAAAAAAASgIAAAAAAAAAAAAAAADwvw" - ], - [ - 512, - 1, - "insert", - { - "characters": "\nreturn" - }, - "CQAAAEsCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABQAgAAAAAAAAAAAABQAgAAAAAAAFQCAAAAAAAAAAAAAFQCAAAAAAAAVQIAAAAAAAAAAAAAVQIAAAAAAABWAgAAAAAAAAAAAABWAgAAAAAAAFcCAAAAAAAAAAAAAFcCAAAAAAAAWAIAAAAAAAAAAAAAWAIAAAAAAABZAgAAAAAAAAAAAABZAgAAAAAAAFoCAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAEsCAAAAAAAASwIAAAAAAAAAAAAAAADwvw" - ], - [ - 513, - 1, - "insert", - { - "characters": " sek" - }, - "BAAAAFoCAAAAAAAAWwIAAAAAAAAAAAAAWwIAAAAAAABcAgAAAAAAAAAAAABcAgAAAAAAAF0CAAAAAAAAAAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAFoCAAAAAAAAWgIAAAAAAAAAAAAAAADwvw" - ], - [ - 514, - 1, - "insert", - { - "characters": "f" - }, - "AQAAAF4CAAAAAAAAXwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF4CAAAAAAAAXgIAAAAAAAAAAAAAAADwvw" - ], - [ - 515, - 2, - "left_delete", - null, - "AgAAAF4CAAAAAAAAXgIAAAAAAAABAAAAZl0CAAAAAAAAXQIAAAAAAAABAAAAaw", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF8CAAAAAAAAXwIAAAAAAAAAAAAAAADwvw" - ], - [ - 516, - 1, - "insert", - { - "characters": "lf.d" - }, - "BAAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAGACAAAAAAAAYQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAF0CAAAAAAAAXQIAAAAAAAAAAAAAAADwvw" - ], - [ - 517, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"session_name\":\"LSP-pyright\",\"item\":{\"kind\":6,\"sortText\":\"05.0001.db_dal\",\"data\":{\"filePath\":\"/home/yannic/work/stacosys/stacosys/db/__init__.py\",\"position\":{\"line\":25,\"character\":21},\"symbolLabel\":\"db_dal\",\"workspacePath\":\"/home/yannic/work/stacosys\"},\"label\":\"db_dal\"}}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "db_dal" - }, - "AgAAAGACAAAAAAAAYAIAAAAAAAABAAAAZGACAAAAAAAAZgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAGECAAAAAAAAYQIAAAAAAAAAAAAAAADwvw" - ], - [ - 524, - 1, - "insert", - { - "characters": "get" - }, - "BAAAAI0CAAAAAAAAjgIAAAAAAAAAAAAAjgIAAAAAAACOAgAAAAAAAAYAAABkYl9kYWyOAgAAAAAAAI8CAAAAAAAAAAAAAI8CAAAAAAAAkAIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAI0CAAAAAAAAkwIAAAAAAAAAAAAAAADwvw" - ], - [ - 525, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkAIAAAAAAAAAAAAAAADwvw" - ], - [ - 530, - 1, - "right_delete", - null, - "AQAAAJACAAAAAAAAkAIAAAAAAAACAAAAKCk", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkgIAAAAAAAD////////vfw" - ], - [ - 537, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAJACAAAAAAAAkgIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJACAAAAAAAAkAIAAAAAAAAAAAAAACBjQA" - ], - [ - 549, - 1, - "left_delete", - null, - "AQAAAIACAAAAAAAAgAIAAAAAAAABAAAAYg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIECAAAAAAAAgQIAAAAAAAAAAAAAAADwvw" - ], - [ - 550, - 1, - "insert", - { - "characters": "al" - }, - "AgAAAIACAAAAAAAAgQIAAAAAAAAAAAAAgQIAAAAAAACCAgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" - ], - [ - 552, - 2, - "right_delete", - null, - "AgAAAJECAAAAAAAAkQIAAAAAAAABAAAAKJECAAAAAAAAkQIAAAAAAAABAAAAKQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAJECAAAAAAAAkQIAAAAAAAAAAAAAAADwvw" - ], - [ - 559, - 1, - "insert", - { - "characters": "db" - }, - "AwAAAH8CAAAAAAAAgAIAAAAAAAAAAAAAgAIAAAAAAACAAgAAAAAAAAMAAABkYWyAAgAAAAAAAIECAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8CAAAAAAAAggIAAAAAAAAAAAAAAADwvw" - ], - [ - 577, - 1, - "insert", - { - "characters": "\n" - }, - "AQAAAH4CAAAAAAAAfwIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH4CAAAAAAAAfgIAAAAAAAAAAAAAAADwvw" - ], - [ - 581, - 1, - "insert", - { - "characters": "al" - }, - "AwAAAIECAAAAAAAAggIAAAAAAAAAAAAAggIAAAAAAACCAgAAAAAAAAEAAABiggIAAAAAAACDAgAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIECAAAAAAAAggIAAAAAAAAAAAAAAADwvw" - ], - [ - 583, - 1, - "left_delete", - null, - "AQAAAH4CAAAAAAAAfgIAAAAAAAABAAAACg", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAH8CAAAAAAAAfwIAAAAAAAAAAAAAAAA7QA" - ], - [ - 590, - 2, - "left_delete", - null, - "AgAAAIECAAAAAAAAgQIAAAAAAAABAAAAbIACAAAAAAAAgAIAAAAAAAABAAAAYQ", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIICAAAAAAAAggIAAAAAAAAAAAAAAADwvw" - ], - [ - 591, - 1, - "insert", - { - "characters": "b" - }, - "AQAAAIACAAAAAAAAgQIAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAIACAAAAAAAAgAIAAAAAAAAAAAAAAADwvw" - ], - [ - 600, - 1, - "", - null, - "AQAAALQAAAAAAAAAwQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" - ], - [ - 602, - 1, - "insert", - { - "characters": "," - }, - "AQAAALQAAAAAAAAAtQAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALQAAAAAAAAAtAAAAAAAAAAAAAAAAADwvw" - ], - [ - 603, - 1, - "insert", - { - "characters": " " - }, - "AQAAALUAAAAAAAAAtgAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAALUAAAAAAAAAtQAAAAAAAAAAAAAAAADwvw" - ], - [ - 612, - 1, - "insert", - { - "characters": "True" - }, - "BQAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAUAAABGYWxzZb8AAAAAAAAAwAAAAAAAAAAAAAAAwAAAAAAAAADBAAAAAAAAAAAAAADBAAAAAAAAAMIAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAL4AAAAAAAAAwwAAAAAAAAAAAAAAAADwvw" - ], - [ - 623, - 1, - "insert", - { - "characters": "False" - }, - "BgAAAL4AAAAAAAAAvwAAAAAAAAAAAAAAvwAAAAAAAAC/AAAAAAAAAAQAAABUcnVlvwAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAMEAAAAAAAAAAAAAAMEAAAAAAAAAwgAAAAAAAAAAAAAAwgAAAAAAAADDAAAAAAAAAAAAAAA", - "AwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAQAAAL4AAAAAAAAAwgAAAAAAAAAAAAAAAADwvw" - ], - [ - 14, - 1, - "insert", - { - "characters": "," - }, - "AQAAAJEAAAAAAAAAkgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACRAAAAAAAAAJEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 15, - 1, - "insert", - { - "characters": " migra" - }, - "BgAAAJIAAAAAAAAAkwAAAAAAAAAAAAAAkwAAAAAAAACUAAAAAAAAAAAAAACUAAAAAAAAAJUAAAAAAAAAAAAAAJUAAAAAAAAAlgAAAAAAAAAAAAAAlgAAAAAAAACXAAAAAAAAAAAAAACXAAAAAAAAAJgAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACSAAAAAAAAAJIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 16, - 1, - "insert", - { - "characters": "te" - }, - "AgAAAJgAAAAAAAAAmQAAAAAAAAAAAAAAmQAAAAAAAACaAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACYAAAAAAAAAJgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 17, - 1, - "insert", - { - "characters": "=False" - }, - "BgAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACcAAAAAAAAAAAAAACcAAAAAAAAAJ0AAAAAAAAAAAAAAJ0AAAAAAAAAngAAAAAAAAAAAAAAngAAAAAAAACfAAAAAAAAAAAAAACfAAAAAAAAAKAAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 19, - 1, - "left_delete", - null, - "AQAAAJkAAAAAAAAAmQAAAAAAAAABAAAAZQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 20, - 1, - "insert", - { - "characters": "ion" - }, - "AwAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 21, - 2, - "left_delete", - null, - "AgAAAJsAAAAAAAAAmwAAAAAAAAABAAAAbpoAAAAAAAAAmgAAAAAAAAABAAAAbw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 22, - 1, - "insert", - { - "characters": "e" - }, - "AQAAAJoAAAAAAAAAmwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACaAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 23, - 2, - "left_delete", - null, - "AgAAAJoAAAAAAAAAmgAAAAAAAAABAAAAZZkAAAAAAAAAmQAAAAAAAAABAAAAaQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACbAAAAAAAAAJsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 24, - 1, - "insert", - { - "characters": "e" - }, - "AQAAAJkAAAAAAAAAmgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 28, - 1, - "insert", - { - "characters": "mir" - }, - "BAAAAM0AAAAAAAAAzgAAAAAAAAAAAAAAzgAAAAAAAADOAAAAAAAAAAUAAABGYWxzZc4AAAAAAAAAzwAAAAAAAAAAAAAAzwAAAAAAAADQAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADNAAAAAAAAANIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 29, - 1, - "left_delete", - null, - "AQAAAM8AAAAAAAAAzwAAAAAAAAABAAAAcg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 30, - 1, - "insert", - { - "characters": "rgation" - }, - "BwAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA0AAAAAAAAADRAAAAAAAAAAAAAADRAAAAAAAAANIAAAAAAAAAAAAAANIAAAAAAAAA0wAAAAAAAAAAAAAA0wAAAAAAAADUAAAAAAAAAAAAAADUAAAAAAAAANUAAAAAAAAAAAAAANUAAAAAAAAA1gAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADPAAAAAAAAAM8AAAAAAAAAAAAAAAAA8L8" - ], - [ - 32, - 1, - "left_delete", - null, - "AQAAAM8AAAAAAAAAzwAAAAAAAAABAAAAcg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 34, - 1, - "insert", - { - "characters": "r" - }, - "AQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADQAAAAAAAAANAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 43, - 1, - "insert", - { - "characters": "ion" - }, - "BAAAAJkAAAAAAAAAmgAAAAAAAAAAAAAAmgAAAAAAAACaAAAAAAAAAAEAAABlmgAAAAAAAACbAAAAAAAAAAAAAACbAAAAAAAAAJwAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 44, - 3, - "left_delete", - null, - "AwAAAJsAAAAAAAAAmwAAAAAAAAABAAAAbpoAAAAAAAAAmgAAAAAAAAABAAAAb5kAAAAAAAAAmQAAAAAAAAABAAAAaQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 45, - 1, - "insert", - { - "characters": "e" - }, - "AQAAAJkAAAAAAAAAmgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACZAAAAAAAAAJkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 49, - 1, - "insert", - { - "characters": "e" - }, - "AgAAANMAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADUAAAAAAAAAAMAAABpb24", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADTAAAAAAAAANYAAAAAAAAAAAAAAAAA8L8" - ], - [ - 59, - 1, - "right_delete", - null, - "AQAAAJEAAAAAAAAAkQAAAAAAAAAPAAAALCBtaWdyYXRlPUZhbHNl", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACRAAAAAAAAAKAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 63, - 1, - "insert", - { - "characters": "\n" - }, - "AwAAAJMAAAAAAAAAlAAAAAAAAAAAAAAAlAAAAAAAAACYAAAAAAAAAAAAAACYAAAAAAAAAJwAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACTAAAAAAAAAJMAAAAAAAAAAAAAAAAAckA" - ], - [ - 64, - 1, - "paste", - null, - "AQAAAJwAAAAAAAAAqQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 66, - 1, - "insert", - { - "characters": "if" - }, - "AgAAAJwAAAAAAAAAnQAAAAAAAAAAAAAAnQAAAAAAAACeAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACcAAAAAAAAAJwAAAAAAAAAAAAAAAAAUkA" - ], - [ - 67, - 1, - "insert", - { - "characters": " db" - }, - "AwAAAJ4AAAAAAAAAnwAAAAAAAAAAAAAAnwAAAAAAAACgAAAAAAAAAAAAAACgAAAAAAAAAKEAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACeAAAAAAAAAJ4AAAAAAAAAAAAAAAAA8L8" - ], - [ - 68, - 1, - "insert", - { - "characters": "_uri" - }, - "BAAAAKEAAAAAAAAAogAAAAAAAAAAAAAAogAAAAAAAACjAAAAAAAAAAAAAACjAAAAAAAAAKQAAAAAAAAAAAAAAKQAAAAAAAAApQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAChAAAAAAAAAKEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 69, - 1, - "insert", - { - "characters": ".st" - }, - "AwAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" - ], - [ - 70, - 1, - "insert", - { - "characters": " " - }, - "AQAAAKgAAAAAAAAAqQAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 72, - 1, - "insert", - { - "characters": "arts" - }, - "BAAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 73, - 1, - "left_delete", - null, - "AQAAAKsAAAAAAAAAqwAAAAAAAAABAAAAcw", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 74, - 1, - "insert", - { - "characters": "w" - }, - "AQAAAKsAAAAAAAAArAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 75, - 3, - "left_delete", - null, - "AwAAAKsAAAAAAAAAqwAAAAAAAAABAAAAd6oAAAAAAAAAqgAAAAAAAAABAAAAdKkAAAAAAAAAqQAAAAAAAAABAAAAcg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAAA8L8" - ], - [ - 78, - 1, - "insert", - { - "characters": "\n\"" - }, - "AwAAAKoAAAAAAAAAqwAAAAAAAAAAAAAAqwAAAAAAAACzAAAAAAAAAAAAAACzAAAAAAAAALQAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACqAAAAAAAAAKoAAAAAAAAAAAAAAAAA8L8" - ], - [ - 79, - 1, - "left_delete", - null, - "AQAAALMAAAAAAAAAswAAAAAAAAABAAAAIg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAC0AAAAAAAAALQAAAAAAAAAAAAAAAAA8L8" - ], - [ - 80, - 1, - "insert", - { - "characters": "#" - }, - "AQAAALMAAAAAAAAAtAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACzAAAAAAAAALMAAAAAAAAAAAAAAAAA8L8" - ], - [ - 83, - 4, - "left_delete", - null, - "BAAAAKgAAAAAAAAAqAAAAAAAAAABAAAAYacAAAAAAAAApwAAAAAAAAABAAAAdKYAAAAAAAAApgAAAAAAAAABAAAAc6UAAAAAAAAApQAAAAAAAAABAAAALg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACpAAAAAAAAAKkAAAAAAAAAAAAAAAAA8L8" - ], - [ - 84, - 1, - "insert", - { - "characters": ".st" - }, - "AwAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACnAAAAAAAAAAAAAACnAAAAAAAAAKgAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" - ], - [ - 85, - 1, - "insert", - { - "characters": "art" - }, - "AwAAAKgAAAAAAAAAqQAAAAAAAAAAAAAAqQAAAAAAAACqAAAAAAAAAAAAAACqAAAAAAAAAKsAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACoAAAAAAAAAKgAAAAAAAAAAAAAAAAA8L8" - ], - [ - 86, - 6, - "left_delete", - null, - "BgAAAKoAAAAAAAAAqgAAAAAAAAABAAAAdKkAAAAAAAAAqQAAAAAAAAABAAAAcqgAAAAAAAAAqAAAAAAAAAABAAAAYacAAAAAAAAApwAAAAAAAAABAAAAdKYAAAAAAAAApgAAAAAAAAABAAAAc6UAAAAAAAAApQAAAAAAAAABAAAALg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACrAAAAAAAAAKsAAAAAAAAAAAAAAAAA8L8" - ], - [ - 87, - 1, - "insert_snippet", - { - "contents": "[$0]" - }, - "AQAAAKUAAAAAAAAApwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKUAAAAAAAAAAAAAAAAA8L8" - ], - [ - 94, - 1, - "insert", - { - "characters": ".startswith" - }, - "DAAAAKUAAAAAAAAApgAAAAAAAAAAAAAApgAAAAAAAACmAAAAAAAAAAIAAABbXaYAAAAAAAAApwAAAAAAAAAAAAAApwAAAAAAAACoAAAAAAAAAAAAAACoAAAAAAAAAKkAAAAAAAAAAAAAAKkAAAAAAAAAqgAAAAAAAAAAAAAAqgAAAAAAAACrAAAAAAAAAAAAAACrAAAAAAAAAKwAAAAAAAAAAAAAAKwAAAAAAAAArQAAAAAAAAAAAAAArQAAAAAAAACuAAAAAAAAAAAAAACuAAAAAAAAAK8AAAAAAAAAAAAAAK8AAAAAAAAAsAAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAClAAAAAAAAAKcAAAAAAAAAAAAAAAAA8L8" - ], - [ - 95, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAALAAAAAAAAAAsgAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACwAAAAAAAAALAAAAAAAAAAAAAAAAAA8L8" - ], - [ - 102, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAALEAAAAAAAAAswAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACxAAAAAAAAALEAAAAAAAAAAAAAAAAA8L8" - ], - [ - 103, - 1, - "paste", - null, - "AQAAALIAAAAAAAAAvwAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACyAAAAAAAAALIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 106, - 1, - "cut", - null, - "AQAAAJ8AAAAAAAAAnwAAAAAAAAAiAAAAZGJfdXJpLnN0YXJ0c3dpdGgoInNxbGl0ZTptZW1vcnkiKQ", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADBAAAAAAAAAJ8AAAAAAAAAAAAAAAAA8L8" - ], - [ - 111, - 1, - "paste", - null, - "AgAAAOIAAAAAAAAABAEAAAAAAAAAAAAABAEAAAAAAAAEAQAAAAAAAAcAAABtaWdyYXRl", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADpAAAAAAAAAOIAAAAAAAAAAAAAAAAA8L8" - ], - [ - 115, - 1, - "right_delete", - null, - "AQAAAJQAAAAAAAAAlAAAAAAAAAAkAAAAICAgICAgICBpZiAgCiAgICAgICAgI3NxbGl0ZTptZW1vcnkK", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACUAAAAAAAAALgAAAAAAAAAAAAAAAAAAAA" - ] - ] - }, - { - "file": "tests/test_api.py", - "settings": - { - "buffer_size": 1625, - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 3, - 1, - "insert", - { - "characters": "," - }, - "AQAAAH4CAAAAAAAAfwIAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB+AgAAAAAAAH4CAAAAAAAAAAAAAAAA8L8" - ], - [ - 4, - 1, - "insert", - { - "characters": " " - }, - "AQAAAH8CAAAAAAAAgAIAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAB/AgAAAAAAAH8CAAAAAAAAAAAAAAAA8L8" - ], - [ - 5, - 2, - "left_delete", - null, - "AgAAAH8CAAAAAAAAfwIAAAAAAAABAAAAIH4CAAAAAAAAfgIAAAAAAAABAAAALA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACAAgAAAAAAAIACAAAAAAAAAAAAAAAA8L8" - ], - [ - 6, - 1, - "revert", - null, - "AgAAAAAAAAAAAAAAAAAAAAAAAAB3BgAAIyEvdXNyL2Jpbi9weXRob24KIyAtKi0gY29kaW5nOiBVVEYtOCAtKi0KCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCgppbXBvcnQgcHl0ZXN0Cgpmcm9tIHN0YWNvc3lzLmRiIGltcG9ydCBkYXRhYmFzZSwgZGFvCmZyb20gc3RhY29zeXMuaW50ZXJmYWNlIGltcG9ydCBhcGkKZnJvbSBzdGFjb3N5cy5pbnRlcmZhY2UgaW1wb3J0IGFwcAoKCmRlZiBpbml0X3Rlc3RfZGIoKToKICAgIGMxID0gZGFvLmNyZWF0ZV9jb21tZW50KCIvc2l0ZTEiLCAiQm9iIiwgIi9ib2Iuc2l0ZSIsICIiLCAiY29tbWVudCAxIikKICAgIGMyID0gZGFvLmNyZWF0ZV9jb21tZW50KCIvc2l0ZTIiLCAiQmlsbCIsICIvYmlsbC5zaXRlIiwgIiIsICJjb21tZW50IDIiKQogICAgYzMgPSBkYW8uY3JlYXRlX2NvbW1lbnQoIi9zaXRlMyIsICJKYWNrIiwgIi9qYWNrLnNpdGUiLCAiIiwgImNvbW1lbnQgMyIpCiAgICBkYW8ucHVibGlzaF9jb21tZW50KGMxKQogICAgZGFvLnB1Ymxpc2hfY29tbWVudChjMykKICAgIGFzc2VydCBjMgoKCkBweXRlc3QuZml4dHVyZQpkZWYgY2xpZW50KCk6CiAgICBsb2dnZXIgPSBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXykKICAgIGRhdGFiYXNlLmNvbmZpZ3VyZSgic3FsaXRlOm1lbW9yeTovL2RiLnNxbGl0ZSIpCiAgICBpbml0X3Rlc3RfZGIoKQogICAgbG9nZ2VyLmluZm8oZiJzdGFydCBpbnRlcmZhY2Uge2FwaX0iKQogICAgcmV0dXJuIGFwcC50ZXN0X2NsaWVudCgpCgoKZGVmIHRlc3RfYXBpX3BpbmcoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL3BpbmciKQogICAgYXNzZXJ0IHJlc3AuZGF0YSA9PSBiIk9LIgoKCmRlZiB0ZXN0X2FwaV9jb3VudF9nbG9iYWwoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzL2NvdW50IikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBkWyJjb3VudCJdID09IDIKCgpkZWYgdGVzdF9hcGlfY291bnRfdXJsKGNsaWVudCk6CiAgICByZXNwID0gY2xpZW50LmdldCgiL2FwaS9jb21tZW50cy9jb3VudD91cmw9L3NpdGUxIikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBkWyJjb3VudCJdID09IDEKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzL2NvdW50P3VybD0vc2l0ZTIiKQogICAgZCA9IGpzb24ubG9hZHMocmVzcC5kYXRhKQogICAgYXNzZXJ0IGQgYW5kIGRbImNvdW50Il0gPT0gMAoKCmRlZiB0ZXN0X2FwaV9jb21tZW50KGNsaWVudCk6CiAgICByZXNwID0gY2xpZW50LmdldCgiL2FwaS9jb21tZW50cz91cmw9L3NpdGUxIikKICAgIGQgPSBqc29uLmxvYWRzKHJlc3AuZGF0YSkKICAgIGFzc2VydCBkIGFuZCBsZW4oZFsiZGF0YSJdKSA9PSAxCiAgICBjb21tZW50ID0gZFsiZGF0YSJdWzBdCiAgICBhc3NlcnQgY29tbWVudFsiYXV0aG9yIl0gPT0gIkJvYiIKICAgIGFzc2VydCBjb21tZW50WyJjb250ZW50Il0gPT0gImNvbW1lbnQgMSIKCgpkZWYgdGVzdF9hcGlfY29tbWVudF9ub3RfZm91bmQoY2xpZW50KToKICAgIHJlc3AgPSBjbGllbnQuZ2V0KCIvYXBpL2NvbW1lbnRzP3VybD0vc2l0ZTIiKQogICAgZCA9IGpzb24ubG9hZHMocmVzcC5kYXRhKQogICAgYXNzZXJ0IGQgYW5kIGRbImRhdGEiXSA9PSBbXQoAAAAAAAAAAFkGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuwIAAAAAAAC7AgAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "tests/test_db.py", - "settings": - { - "buffer_size": 2740, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 7, - 1, - "insert", - { - "characters": " " - }, - "AQAAAHcAAAAAAAAAeAAAAAAAAAAAAAAA", - "AQAAAA" - ], - [ - 10, - 1, - "trim_trailing_white_space", - null, - "AQAAAHcAAAAAAAAAdwAAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAlQAAAAAAAACVAAAAAAAAAAAAAAAAAPC/" - ], - [ - 23, - 1, - "insert", - { - "characters": "\ndef" - }, - "BQAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADNAAAAAAAAAAEAAAAKzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzQAAAAAAAADMAAAAAAAAAAAAAAAAAPC/" - ], - [ - 24, - 1, - "insert", - { - "characters": " test" - }, - "BQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA0QAAAAAAAADSAAAAAAAAAAAAAADSAAAAAAAAANMAAAAAAAAAAAAAANMAAAAAAAAA1AAAAAAAAAAAAAAA1AAAAAAAAADVAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0AAAAAAAAADQAAAAAAAAAAAAAAAAAPC/" - ], - [ - 25, - 1, - "insert", - { - "characters": "_findc" - }, - "BgAAANUAAAAAAAAA1gAAAAAAAAAAAAAA1gAAAAAAAADXAAAAAAAAAAAAAADXAAAAAAAAANgAAAAAAAAAAAAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1QAAAAAAAADVAAAAAAAAAAAAAAAAAPC/" - ], - [ - 26, - 1, - "left_delete", - null, - "AQAAANoAAAAAAAAA2gAAAAAAAAABAAAAYw", - "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" - ], - [ - 27, - 1, - "insert", - { - "characters": "_comment_" - }, - "CQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAADfAAAAAAAAAOAAAAAAAAAAAAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" - ], - [ - 28, - 1, - "insert", - { - "characters": "by_id" - }, - "BQAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA5wAAAAAAAADoAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 29, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAOgAAAAAAAAA6gAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6AAAAAAAAADoAAAAAAAAAAAAAAAAAPC/" - ], - [ - 32, - 1, - "insert", - { - "characters": "sert" - }, - "BAAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADrAAAAAAAAAAAAAADrAAAAAAAAAOwAAAAAAAAAAAAAAOwAAAAAAAAA7QAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6QAAAAAAAADpAAAAAAAAAAAAAAAAAPC/" - ], - [ - 33, - 2, - "left_delete", - null, - "AgAAAOwAAAAAAAAA7AAAAAAAAAABAAAAdOsAAAAAAAAA6wAAAAAAAAABAAAAcg", - "AQAAAAAAAAABAAAA7QAAAAAAAADtAAAAAAAAAAAAAAAAAPC/" - ], - [ - 34, - 1, - "insert", - { - "characters": "t" - }, - "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 35, - 1, - "insert_completion", - { - "completion": "setup_db", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "setup_db" - }, - "AgAAAOkAAAAAAAAA6QAAAAAAAAADAAAAc2V06QAAAAAAAADxAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7AAAAAAAAADsAAAAAAAAAAAAAAAAAPC/" - ], - [ - 37, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAPIAAAAAAAAA8wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA8gAAAAAAAADyAAAAAAAAAAAAAAAAAPC/" - ], - [ - 38, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAPMAAAAAAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAD4AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 48, - 1, - "insert", - { - "characters": "," - }, - "AgAAAPEAAAAAAAAA8gAAAAAAAAAAAAAA9QAAAAAAAAD1AAAAAAAAAAQAAAAgICAg", - "AQAAAAAAAAABAAAA8QAAAAAAAADxAAAAAAAAAAAAAAAAAPC/" - ], - [ - 49, - 1, - "insert", - { - "characters": " f" - }, - "AgAAAPIAAAAAAAAA8wAAAAAAAAAAAAAA8wAAAAAAAAD0AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA8gAAAAAAAADyAAAAAAAAAAAAAAAAAPC/" - ], - [ - 50, - 1, - "left_delete", - null, - "AQAAAPMAAAAAAAAA8wAAAAAAAAABAAAAZg", - "AQAAAAAAAAABAAAA9AAAAAAAAAD0AAAAAAAAAAAAAAAAAPC/" - ], - [ - 51, - 2, - "left_delete", - null, - "AgAAAPIAAAAAAAAA8gAAAAAAAAABAAAAIPEAAAAAAAAA8QAAAAAAAAABAAAALA", - "AQAAAAAAAAABAAAA8wAAAAAAAADzAAAAAAAAAAAAAAAAAPC/" - ], - [ - 53, - 1, - "reindent", - null, - "AQAAAPQAAAAAAAAA+AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA9AAAAAAAAAD0AAAAAAAAAAAAAAAAQHRA" - ], - [ - 54, - 1, - "insert", - { - "characters": "asse" - }, - "BAAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" - ], - [ - 55, - 1, - "insert", - { - "characters": "rt" - }, - "AgAAAPwAAAAAAAAA/QAAAAAAAAAAAAAA/QAAAAAAAAD+AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAPC/" - ], - [ - 56, - 1, - "insert", - { - "characters": " " - }, - "AQAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/gAAAAAAAAD+AAAAAAAAAAAAAAAAAPC/" - ], - [ - 58, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAEAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 61, - 1, - "insert", - { - "characters": " " - }, - "AgAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAAAQAAAAgICAg", - "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 66, - 1, - "paste", - null, - "AQAAAP8AAAAAAAAAEQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 67, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAABEBAAAAAAAAEwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEQEAAAAAAAARAQAAAAAAAAAAAAAAAPC/" - ], - [ - 68, - 1, - "insert", - { - "characters": "1" - }, - "AQAAABIBAAAAAAAAEwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 73, - 1, - "insert", - { - "characters": "dao." - }, - "BAAAAP8AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAABAQAAAAAAAAAAAAABAQAAAAAAAAIBAAAAAAAAAAAAAAIBAAAAAAAAAwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAPC/" - ], - [ - 75, - 1, - "trim_trailing_white_space", - null, - "AQAAABgBAAAAAAAAGAEAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAAwEAAAAAAAADAQAAAAAAAAAAAAAAAPC/" - ], - [ - 80, - 1, - "insert", - { - "characters": " is" - }, - "AwAAABgBAAAAAAAAGQEAAAAAAAAAAAAAGQEAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGAEAAAAAAAAYAQAAAAAAAAAAAAAAAPC/" - ], - [ - 81, - 1, - "insert", - { - "characters": " None" - }, - "BQAAABsBAAAAAAAAHAEAAAAAAAAAAAAAHAEAAAAAAAAdAQAAAAAAAAAAAAAdAQAAAAAAAB4BAAAAAAAAAAAAAB4BAAAAAAAAHwEAAAAAAAAAAAAAHwEAAAAAAAAgAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGwEAAAAAAAAbAQAAAAAAAAAAAAAAAPC/" - ], - [ - 84, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAACABAAAAAAAAIQEAAAAAAAAAAAAAIQEAAAAAAAAlAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIAEAAAAAAAAgAQAAAAAAAAAAAAAAAPC/" - ], - [ - 104, - 1, - "paste", - null, - "AQAAACUBAAAAAAAAYgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJQEAAAAAAAAlAQAAAAAAAAAAAAAAAPC/" - ], - [ - 106, - 1, - "insert", - { - "characters": "\nasser" - }, - "BwAAAGIBAAAAAAAAYwEAAAAAAAAAAAAAYwEAAAAAAABnAQAAAAAAAAAAAABnAQAAAAAAAGgBAAAAAAAAAAAAAGgBAAAAAAAAaQEAAAAAAAAAAAAAaQEAAAAAAABqAQAAAAAAAAAAAABqAQAAAAAAAGsBAAAAAAAAAAAAAGsBAAAAAAAAbAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYgEAAAAAAABiAQAAAAAAAAAAAAAAAPC/" - ], - [ - 107, - 1, - "insert", - { - "characters": "t" - }, - "AQAAAGwBAAAAAAAAbQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbAEAAAAAAABsAQAAAAAAAAAAAAAAAPC/" - ], - [ - 108, - 1, - "insert", - { - "characters": " " - }, - "AQAAAG0BAAAAAAAAbgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbQEAAAAAAABtAQAAAAAAAAAAAAAAAPC/" - ], - [ - 109, - 1, - "insert", - { - "characters": "c1." - }, - "AwAAAG4BAAAAAAAAbwEAAAAAAAAAAAAAbwEAAAAAAABwAQAAAAAAAAAAAABwAQAAAAAAAHEBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" - ], - [ - 110, - 1, - "insert", - { - "characters": "id" - }, - "AgAAAHEBAAAAAAAAcgEAAAAAAAAAAAAAcgEAAAAAAABzAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcQEAAAAAAABxAQAAAAAAAAAAAAAAAPC/" - ], - [ - 111, - 1, - "insert", - { - "characters": " is" - }, - "AwAAAHMBAAAAAAAAdAEAAAAAAAAAAAAAdAEAAAAAAAB1AQAAAAAAAAAAAAB1AQAAAAAAAHYBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcwEAAAAAAABzAQAAAAAAAAAAAAAAAPC/" - ], - [ - 112, - 1, - "insert", - { - "characters": " not" - }, - "BAAAAHYBAAAAAAAAdwEAAAAAAAAAAAAAdwEAAAAAAAB4AQAAAAAAAAAAAAB4AQAAAAAAAHkBAAAAAAAAAAAAAHkBAAAAAAAAegEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgEAAAAAAAB2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 113, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAHoBAAAAAAAAewEAAAAAAAAAAAAAewEAAAAAAAB8AQAAAAAAAAAAAAB8AQAAAAAAAH0BAAAAAAAAAAAAAH0BAAAAAAAAfgEAAAAAAAAAAAAAfgEAAAAAAAB/AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAegEAAAAAAAB6AQAAAAAAAAAAAAAAAPC/" - ], - [ - 117, - 1, - "reindent", - null, - "AQAAAIABAAAAAAAAhAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAgG9A" - ], - [ - 120, - 1, - "insert", - { - "characters": "c2" - }, - "AgAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAEJA" - ], - [ - 121, - 2, - "left_delete", - null, - "AgAAAIUBAAAAAAAAhQEAAAAAAAABAAAAMoQBAAAAAAAAhAEAAAAAAAABAAAAYw", - "AQAAAAAAAAABAAAAhgEAAAAAAACGAQAAAAAAAAAAAAAAAPC/" - ], - [ - 122, - 1, - "insert", - { - "characters": "rec" - }, - "AwAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAACGAQAAAAAAAIcBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" - ], - [ - 123, - 3, - "left_delete", - null, - "AwAAAIYBAAAAAAAAhgEAAAAAAAABAAAAY4UBAAAAAAAAhQEAAAAAAAABAAAAZYQBAAAAAAAAhAEAAAAAAAABAAAAcg", - "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" - ], - [ - 124, - 1, - "insert", - { - "characters": "find_c1" - }, - "BwAAAIQBAAAAAAAAhQEAAAAAAAAAAAAAhQEAAAAAAACGAQAAAAAAAAAAAACGAQAAAAAAAIcBAAAAAAAAAAAAAIcBAAAAAAAAiAEAAAAAAAAAAAAAiAEAAAAAAACJAQAAAAAAAAAAAACJAQAAAAAAAIoBAAAAAAAAAAAAAIoBAAAAAAAAiwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhAEAAAAAAACEAQAAAAAAAAAAAAAAAPC/" - ], - [ - 125, - 1, - "insert", - { - "characters": " " - }, - "AgAAAIsBAAAAAAAAjAEAAAAAAAAAAAAAjAEAAAAAAACNAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiwEAAAAAAACLAQAAAAAAAAAAAAAAAPC/" - ], - [ - 126, - 1, - "left_delete", - null, - "AQAAAIwBAAAAAAAAjAEAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAAjQEAAAAAAACNAQAAAAAAAAAAAAAAAPC/" - ], - [ - 127, - 1, - "insert", - { - "characters": "=" - }, - "AQAAAIwBAAAAAAAAjQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjAEAAAAAAACMAQAAAAAAAAAAAAAAAPC/" - ], - [ - 128, - 1, - "insert", - { - "characters": " " - }, - "AQAAAI0BAAAAAAAAjgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQEAAAAAAACNAQAAAAAAAAAAAAAAAPC/" - ], - [ - 136, - 1, - "paste", - null, - "AQAAAI4BAAAAAAAApwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjgEAAAAAAACOAQAAAAAAAAAAAAAAQHRA" - ], - [ - 139, - 1, - "insert", - { - "characters": "c1.id" - }, - "BgAAAKUBAAAAAAAApgEAAAAAAAAAAAAApgEAAAAAAACmAQAAAAAAAAEAAAAxpgEAAAAAAACnAQAAAAAAAAAAAACnAQAAAAAAAKgBAAAAAAAAAAAAAKgBAAAAAAAAqQEAAAAAAAAAAAAAqQEAAAAAAACqAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAApgEAAAAAAAClAQAAAAAAAAAAAAAAAPC/" - ], - [ - 141, - 1, - "insert", - { - "characters": "\nassert" - }, - "CAAAAKsBAAAAAAAArAEAAAAAAAAAAAAArAEAAAAAAACwAQAAAAAAAAAAAACwAQAAAAAAALEBAAAAAAAAAAAAALEBAAAAAAAAsgEAAAAAAAAAAAAAsgEAAAAAAACzAQAAAAAAAAAAAACzAQAAAAAAALQBAAAAAAAAAAAAALQBAAAAAAAAtQEAAAAAAAAAAAAAtQEAAAAAAAC2AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqwEAAAAAAACrAQAAAAAAAAAAAAAAAPC/" - ], - [ - 142, - 1, - "insert", - { - "characters": " " - }, - "AQAAALYBAAAAAAAAtwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtgEAAAAAAAC2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 143, - 1, - "insert", - { - "characters": "find" - }, - "BAAAALcBAAAAAAAAuAEAAAAAAAAAAAAAuAEAAAAAAAC5AQAAAAAAAAAAAAC5AQAAAAAAALoBAAAAAAAAAAAAALoBAAAAAAAAuwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtwEAAAAAAAC3AQAAAAAAAAAAAAAAAPC/" - ], - [ - 144, - 1, - "insert", - { - "characters": "_c1" - }, - "AwAAALsBAAAAAAAAvAEAAAAAAAAAAAAAvAEAAAAAAAC9AQAAAAAAAAAAAAC9AQAAAAAAAL4BAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAuwEAAAAAAAC7AQAAAAAAAAAAAAAAAPC/" - ], - [ - 145, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"find_c1\",\"position\":{\"line\":17,\"character\":18},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"find_c1\",\"sortText\":\"09.9999.find_c1\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "find_c1" - }, - "AgAAALcBAAAAAAAAtwEAAAAAAAAHAAAAZmluZF9jMbcBAAAAAAAAvgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" - ], - [ - 146, - 1, - "insert", - { - "characters": "\n\n" - }, - "BQAAAL4BAAAAAAAAvwEAAAAAAAAAAAAAvwEAAAAAAADDAQAAAAAAAAAAAADDAQAAAAAAAMQBAAAAAAAAAAAAAMQBAAAAAAAAyAEAAAAAAAAAAAAAvwEAAAAAAAC/AQAAAAAAAAQAAAAgICAg", - "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" - ], - [ - 148, - 1, - "reindent", - null, - "AQAAAL8BAAAAAAAAwwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAEJA" - ], - [ - 149, - 1, - "insert", - { - "characters": "assert" - }, - "BwAAAMMBAAAAAAAAxAEAAAAAAAAAAAAAxQEAAAAAAADFAQAAAAAAAAQAAAAgICAgxAEAAAAAAADFAQAAAAAAAAAAAADFAQAAAAAAAMYBAAAAAAAAAAAAAMYBAAAAAAAAxwEAAAAAAAAAAAAAxwEAAAAAAADIAQAAAAAAAAAAAADIAQAAAAAAAMkBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwwEAAAAAAADDAQAAAAAAAAAAAAAAAPC/" - ], - [ - 150, - 1, - "insert", - { - "characters": " find_" - }, - "BgAAAMkBAAAAAAAAygEAAAAAAAAAAAAAygEAAAAAAADLAQAAAAAAAAAAAADLAQAAAAAAAMwBAAAAAAAAAAAAAMwBAAAAAAAAzQEAAAAAAAAAAAAAzQEAAAAAAADOAQAAAAAAAAAAAADOAQAAAAAAAM8BAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAyQEAAAAAAADJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 157, - 1, - "insert", - { - "characters": "c1.i" - }, - "BAAAAM8BAAAAAAAA0AEAAAAAAAAAAAAA0AEAAAAAAADRAQAAAAAAAAAAAADRAQAAAAAAANIBAAAAAAAAAAAAANIBAAAAAAAA0wEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzwEAAAAAAADPAQAAAAAAAAAAAAAAAPC/" - ], - [ - 158, - 1, - "insert", - { - "characters": "d" - }, - "AQAAANMBAAAAAAAA1AEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0wEAAAAAAADTAQAAAAAAAAAAAAAAAPC/" - ], - [ - 159, - 1, - "insert", - { - "characters": " " - }, - "AQAAANQBAAAAAAAA1QEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1AEAAAAAAADUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 160, - 3, - "left_delete", - null, - "AwAAANQBAAAAAAAA1AEAAAAAAAABAAAAINMBAAAAAAAA0wEAAAAAAAABAAAAZNIBAAAAAAAA0gEAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" - ], - [ - 162, - 1, - "insert", - { - "characters": "id" - }, - "AgAAANIBAAAAAAAA0wEAAAAAAAAAAAAA0wEAAAAAAADUAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0gEAAAAAAADSAQAAAAAAAAAAAAAAAPC/" - ], - [ - 163, - 1, - "insert", - { - "characters": " =" - }, - "AgAAANQBAAAAAAAA1QEAAAAAAAAAAAAA1QEAAAAAAADWAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1AEAAAAAAADUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 164, - 1, - "insert", - { - "characters": " c1." - }, - "BAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA1wEAAAAAAADYAQAAAAAAAAAAAADYAQAAAAAAANkBAAAAAAAAAAAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1gEAAAAAAADWAQAAAAAAAAAAAAAAAPC/" - ], - [ - 165, - 1, - "insert", - { - "characters": "id" - }, - "AgAAANoBAAAAAAAA2wEAAAAAAAAAAAAA2wEAAAAAAADcAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gEAAAAAAADaAQAAAAAAAAAAAAAAAPC/" - ], - [ - 166, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAANwBAAAAAAAA3QEAAAAAAAAAAAAA3QEAAAAAAADhAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3AEAAAAAAADcAQAAAAAAAAAAAAAAAPC/" - ], - [ - 168, - 1, - "trim_trailing_white_space", - null, - "AQAAAN0BAAAAAAAA3QEAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAA4QEAAAAAAADhAQAAAAAAAAAAAAAAAPC/" - ], - [ - 172, - 1, - "insert", - { - "characters": "=" - }, - "AQAAANUBAAAAAAAA1gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1QEAAAAAAADVAQAAAAAAAAAAAAAAAPC/" - ], - [ - 178, - 1, - "insert", - { - "characters": "\ndef" - }, - "BAAAAMwAAAAAAAAAzQAAAAAAAAAAAAAAzQAAAAAAAADOAAAAAAAAAAAAAADOAAAAAAAAAM8AAAAAAAAAAAAAAM8AAAAAAAAA0AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzAAAAAAAAADMAAAAAAAAAAAAAAAA4GlA" - ], - [ - 179, - 1, - "insert", - { - "characters": " " - }, - "AQAAANAAAAAAAAAA0QAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0AAAAAAAAADQAAAAAAAAAAAAAAAAAPC/" - ], - [ - 180, - 1, - "insert", - { - "characters": "is_" - }, - "AwAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADTAAAAAAAAAAAAAADTAAAAAAAAANQAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0QAAAAAAAADRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 181, - 3, - "left_delete", - null, - "AwAAANMAAAAAAAAA0wAAAAAAAAABAAAAX9IAAAAAAAAA0gAAAAAAAAABAAAAc9EAAAAAAAAA0QAAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAA1AAAAAAAAADUAAAAAAAAAAAAAAAAAPC/" - ], - [ - 182, - 1, - "insert", - { - "characters": "equals_" - }, - "BwAAANEAAAAAAAAA0gAAAAAAAAAAAAAA0gAAAAAAAADTAAAAAAAAAAAAAADTAAAAAAAAANQAAAAAAAAAAAAAANQAAAAAAAAA1QAAAAAAAAAAAAAA1QAAAAAAAADWAAAAAAAAAAAAAADWAAAAAAAAANcAAAAAAAAAAAAAANcAAAAAAAAA2AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0QAAAAAAAADRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 183, - 1, - "insert", - { - "characters": "oco" - }, - "AwAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" - ], - [ - 184, - 3, - "left_delete", - null, - "AwAAANoAAAAAAAAA2gAAAAAAAAABAAAAb9kAAAAAAAAA2QAAAAAAAAABAAAAY9gAAAAAAAAA2AAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" - ], - [ - 185, - 1, - "insert", - { - "characters": "coù" - }, - "AwAAANgAAAAAAAAA2QAAAAAAAAAAAAAA2QAAAAAAAADaAAAAAAAAAAAAAADaAAAAAAAAANsAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2AAAAAAAAADYAAAAAAAAAAAAAAAAAPC/" - ], - [ - 186, - 1, - "left_delete", - null, - "AQAAANoAAAAAAAAA2gAAAAAAAAACAAAAw7k", - "AQAAAAAAAAABAAAA2wAAAAAAAADbAAAAAAAAAAAAAAAAAPC/" - ], - [ - 187, - 1, - "insert", - { - "characters": "mment" - }, - "BQAAANoAAAAAAAAA2wAAAAAAAAAAAAAA2wAAAAAAAADcAAAAAAAAAAAAAADcAAAAAAAAAN0AAAAAAAAAAAAAAN0AAAAAAAAA3gAAAAAAAAAAAAAA3gAAAAAAAADfAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gAAAAAAAADaAAAAAAAAAAAAAAAAAPC/" - ], - [ - 188, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAN8AAAAAAAAA4QAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3wAAAAAAAADfAAAAAAAAAAAAAAAAAPC/" - ], - [ - 189, - 1, - "insert", - { - "characters": "c" - }, - "AQAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4AAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 190, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAOEAAAAAAAAA4gAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4QAAAAAAAADhAAAAAAAAAAAAAAAAAPC/" - ], - [ - 191, - 1, - "insert", - { - "characters": " c" - }, - "AgAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA4wAAAAAAAADkAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" - ], - [ - 192, - 1, - "left_delete", - null, - "AQAAAOMAAAAAAAAA4wAAAAAAAAABAAAAYw", - "AQAAAAAAAAABAAAA5AAAAAAAAADkAAAAAAAAAAAAAAAAAPC/" - ], - [ - 193, - 1, - "insert", - { - "characters": "Comment," - }, - "CAAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAOYAAAAAAAAA5wAAAAAAAAAAAAAA5wAAAAAAAADoAAAAAAAAAAAAAADoAAAAAAAAAOkAAAAAAAAAAAAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADrAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 194, - 1, - "insert", - { - "characters": " " - }, - "AQAAAOsAAAAAAAAA7AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6wAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 206, - 1, - "right_delete", - null, - "AQAAAOEAAAAAAAAA4QAAAAAAAAAJAAAAOiBDb21tZW50", - "AQAAAAAAAAABAAAA6gAAAAAAAADhAAAAAAAAAAAAAAAAAPC/" - ], - [ - 208, - 1, - "insert", - { - "characters": "c1," - }, - "BAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADhAAAAAAAAAAEAAABj4QAAAAAAAADiAAAAAAAAAAAAAADiAAAAAAAAAOMAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4QAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 209, - 1, - "insert", - { - "characters": " c2" - }, - "AwAAAOMAAAAAAAAA5AAAAAAAAAAAAAAA5AAAAAAAAADlAAAAAAAAAAAAAADlAAAAAAAAAOYAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 210, - 2, - "right_delete", - null, - "AgAAAOYAAAAAAAAA5gAAAAAAAAABAAAALOYAAAAAAAAA5gAAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAA5gAAAAAAAADmAAAAAAAAAAAAAAAAAPC/" - ], - [ - 218, - 1, - "insert", - { - "characters": "com" - }, - "BAAAAOAAAAAAAAAA4QAAAAAAAAAAAAAA4QAAAAAAAADhAAAAAAAAAAIAAABjMeEAAAAAAAAA4gAAAAAAAAAAAAAA4gAAAAAAAADjAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gAAAAAAAADgAAAAAAAAAAAAAAAAAPC/" - ], - [ - 219, - 1, - "left_delete", - null, - "AQAAAOIAAAAAAAAA4gAAAAAAAAABAAAAbQ", - "AQAAAAAAAAABAAAA4wAAAAAAAADjAAAAAAAAAAAAAAAAAPC/" - ], - [ - 220, - 1, - "insert", - { - "characters": "mment" - }, - "BQAAAOIAAAAAAAAA4wAAAAAAAAAAAAAA4wAAAAAAAADkAAAAAAAAAAAAAADkAAAAAAAAAOUAAAAAAAAAAAAAAOUAAAAAAAAA5gAAAAAAAAAAAAAA5gAAAAAAAADnAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4gAAAAAAAADiAAAAAAAAAAAAAAAAAPC/" - ], - [ - 227, - 1, - "insert", - { - "characters": "other" - }, - "BgAAAOkAAAAAAAAA6gAAAAAAAAAAAAAA6gAAAAAAAADqAAAAAAAAAAIAAABjMuoAAAAAAAAA6wAAAAAAAAAAAAAA6wAAAAAAAADsAAAAAAAAAAAAAADsAAAAAAAAAO0AAAAAAAAAAAAAAO0AAAAAAAAA7gAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6QAAAAAAAADrAAAAAAAAAAAAAAAAAPC/" - ], - [ - 229, - 1, - "insert", - { - "characters": ":" - }, - "AQAAAO8AAAAAAAAA8AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7wAAAAAAAADvAAAAAAAAAAAAAAAAAPC/" - ], - [ - 230, - 1, - "insert", - { - "characters": "\nret" - }, - "BQAAAPAAAAAAAAAA8QAAAAAAAAAAAAAA8QAAAAAAAAD1AAAAAAAAAAAAAAD1AAAAAAAAAPYAAAAAAAAAAAAAAPYAAAAAAAAA9wAAAAAAAAAAAAAA9wAAAAAAAAD4AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAPC/" - ], - [ - 231, - 1, - "insert", - { - "characters": "urn" - }, - "AwAAAPgAAAAAAAAA+QAAAAAAAAAAAAAA+QAAAAAAAAD6AAAAAAAAAAAAAAD6AAAAAAAAAPsAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAPC/" - ], - [ - 232, - 1, - "insert", - { - "characters": " comment" - }, - "CAAAAPsAAAAAAAAA/AAAAAAAAAAAAAAA/AAAAAAAAAD9AAAAAAAAAAAAAAD9AAAAAAAAAP4AAAAAAAAAAAAAAP4AAAAAAAAA/wAAAAAAAAAAAAAA/wAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAEBAAAAAAAAAAAAAAEBAAAAAAAAAgEAAAAAAAAAAAAAAgEAAAAAAAADAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+wAAAAAAAAD7AAAAAAAAAAAAAAAAAPC/" - ], - [ - 233, - 1, - "insert", - { - "characters": ".id" - }, - "AwAAAAMBAAAAAAAABAEAAAAAAAAAAAAABAEAAAAAAAAFAQAAAAAAAAAAAAAFAQAAAAAAAAYBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAwEAAAAAAAADAQAAAAAAAAAAAAAAAPC/" - ], - [ - 234, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAAYBAAAAAAAABwEAAAAAAAAAAAAABwEAAAAAAAAIAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABgEAAAAAAAAGAQAAAAAAAAAAAAAAAPC/" - ], - [ - 235, - 1, - "insert", - { - "characters": " " - }, - "AQAAAAgBAAAAAAAACQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACAEAAAAAAAAIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 236, - 1, - "left_delete", - null, - "AQAAAAgBAAAAAAAACAEAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 237, - 1, - "insert", - { - "characters": "=" - }, - "AQAAAAgBAAAAAAAACQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACAEAAAAAAAAIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 238, - 1, - "insert", - { - "characters": " other.id" - }, - "CQAAAAkBAAAAAAAACgEAAAAAAAAAAAAACgEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADQEAAAAAAAAAAAAADQEAAAAAAAAOAQAAAAAAAAAAAAAOAQAAAAAAAA8BAAAAAAAAAAAAAA8BAAAAAAAAEAEAAAAAAAAAAAAAEAEAAAAAAAARAQAAAAAAAAAAAAARAQAAAAAAABIBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACQEAAAAAAAAJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 239, - 1, - "insert", - { - "characters": " " - }, - "AQAAABIBAAAAAAAAEwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 240, - 1, - "insert", - { - "characters": "\nand" - }, - "BQAAABMBAAAAAAAAFAEAAAAAAAAAAAAAFAEAAAAAAAAYAQAAAAAAAAAAAAAYAQAAAAAAABkBAAAAAAAAAAAAABkBAAAAAAAAGgEAAAAAAAAAAAAAGgEAAAAAAAAbAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEwEAAAAAAAATAQAAAAAAAAAAAAAAAPC/" - ], - [ - 241, - 1, - "insert", - { - "characters": " " - }, - "AQAAABsBAAAAAAAAHAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGwEAAAAAAAAbAQAAAAAAAAAAAAAAAPC/" - ], - [ - 244, - 1, - "insert", - { - "characters": "comment." - }, - "CAAAABwBAAAAAAAAHQEAAAAAAAAAAAAAHQEAAAAAAAAeAQAAAAAAAAAAAAAeAQAAAAAAAB8BAAAAAAAAAAAAAB8BAAAAAAAAIAEAAAAAAAAAAAAAIAEAAAAAAAAhAQAAAAAAAAAAAAAhAQAAAAAAACIBAAAAAAAAAAAAACIBAAAAAAAAIwEAAAAAAAAAAAAAIwEAAAAAAAAkAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHAEAAAAAAAAcAQAAAAAAAAAAAAAAAPC/" - ], - [ - 248, - 1, - "insert", - { - "characters": "\nfrom" - }, - "BQAAAHYAAAAAAAAAdwAAAAAAAAAAAAAAdwAAAAAAAAB4AAAAAAAAAAAAAAB4AAAAAAAAAHkAAAAAAAAAAAAAAHkAAAAAAAAAegAAAAAAAAAAAAAAegAAAAAAAAB7AAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdgAAAAAAAAB2AAAAAAAAAAAAAAAAAPC/" - ], - [ - 249, - 1, - "insert", - { - "characters": " stacoys" - }, - "CAAAAHsAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB9AAAAAAAAAAAAAAB9AAAAAAAAAH4AAAAAAAAAAAAAAH4AAAAAAAAAfwAAAAAAAAAAAAAAfwAAAAAAAACAAAAAAAAAAAAAAACAAAAAAAAAAIEAAAAAAAAAAAAAAIEAAAAAAAAAggAAAAAAAAAAAAAAggAAAAAAAACDAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAewAAAAAAAAB7AAAAAAAAAAAAAAAAAPC/" - ], - [ - 250, - 3, - "left_delete", - null, - "AwAAAIIAAAAAAAAAggAAAAAAAAABAAAAc4EAAAAAAAAAgQAAAAAAAAABAAAAeYAAAAAAAAAAgAAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAgwAAAAAAAACDAAAAAAAAAAAAAAAAAPC/" - ], - [ - 251, - 1, - "insert", - { - "characters": "osys.co" - }, - "BwAAAIAAAAAAAAAAgQAAAAAAAAAAAAAAgQAAAAAAAACCAAAAAAAAAAAAAACCAAAAAAAAAIMAAAAAAAAAAAAAAIMAAAAAAAAAhAAAAAAAAAAAAAAAhAAAAAAAAACFAAAAAAAAAAAAAACFAAAAAAAAAIYAAAAAAAAAAAAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAPC/" - ], - [ - 252, - 1, - "insert", - { - "characters": "mm" - }, - "AgAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 253, - 3, - "left_delete", - null, - "AwAAAIgAAAAAAAAAiAAAAAAAAAABAAAAbYcAAAAAAAAAhwAAAAAAAAABAAAAbYYAAAAAAAAAhgAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAiQAAAAAAAACJAAAAAAAAAAAAAAAAAPC/" - ], - [ - 254, - 1, - "insert", - { - "characters": "m" - }, - "AQAAAIYAAAAAAAAAhwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhgAAAAAAAACGAAAAAAAAAAAAAAAAAPC/" - ], - [ - 255, - 2, - "left_delete", - null, - "AgAAAIYAAAAAAAAAhgAAAAAAAAABAAAAbYUAAAAAAAAAhQAAAAAAAAABAAAAYw", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 256, - 1, - "insert", - { - "characters": "mop" - }, - "AwAAAIUAAAAAAAAAhgAAAAAAAAAAAAAAhgAAAAAAAACHAAAAAAAAAAAAAACHAAAAAAAAAIgAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQAAAAAAAACFAAAAAAAAAAAAAAAAAPC/" - ], - [ - 257, - 1, - "left_delete", - null, - "AQAAAIcAAAAAAAAAhwAAAAAAAAABAAAAcA", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" - ], - [ - 258, - 1, - "insert", - { - "characters": "d" - }, - "AQAAAIcAAAAAAAAAiAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwAAAAAAAACHAAAAAAAAAAAAAAAAAPC/" - ], - [ - 259, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"label\":\"model\",\"sortText\":\"02.9999.model\",\"kind\":9},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "model" - }, - "AgAAAIUAAAAAAAAAhQAAAAAAAAADAAAAbW9khQAAAAAAAACKAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiAAAAAAAAACIAAAAAAAAAAAAAAAAAPC/" - ], - [ - 260, - 1, - "insert", - { - "characters": "i" - }, - "AQAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 261, - 1, - "left_delete", - null, - "AQAAAIoAAAAAAAAAigAAAAAAAAABAAAAaQ", - "AQAAAAAAAAABAAAAiwAAAAAAAACLAAAAAAAAAAAAAAAAAPC/" - ], - [ - 262, - 1, - "insert", - { - "characters": " imort" - }, - "BgAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAACMAAAAAAAAAI0AAAAAAAAAAAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 263, - 1, - "insert", - { - "characters": " " - }, - "AQAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAPC/" - ], - [ - 264, - 4, - "left_delete", - null, - "BAAAAJAAAAAAAAAAkAAAAAAAAAABAAAAII8AAAAAAAAAjwAAAAAAAAABAAAAdI4AAAAAAAAAjgAAAAAAAAABAAAAco0AAAAAAAAAjQAAAAAAAAABAAAAbw", - "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 265, - 1, - "insert", - { - "characters": "port" - }, - "BAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAjQAAAAAAAACNAAAAAAAAAAAAAAAAAPC/" - ], - [ - 266, - 1, - "insert", - { - "characters": " " - }, - "AQAAAJEAAAAAAAAAkgAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAPC/" - ], - [ - 267, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"comment\",\"position\":{\"line\":7,\"character\":27},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"comment\",\"sortText\":\"09.9999.comment\",\"kind\":9},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "comment" - }, - "AQAAAJIAAAAAAAAAmQAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAkgAAAAAAAACSAAAAAAAAAAAAAAAAAPC/" - ], - [ - 274, - 1, - "insert", - { - "characters": ".comment" - }, - "CAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAAiwAAAAAAAACMAAAAAAAAAAAAAACMAAAAAAAAAI0AAAAAAAAAAAAAAI0AAAAAAAAAjgAAAAAAAAAAAAAAjgAAAAAAAACPAAAAAAAAAAAAAACPAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAAkQAAAAAAAAAAAAAAkQAAAAAAAACSAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAigAAAAAAAACKAAAAAAAAAAAAAAAAAPC/" - ], - [ - 280, - 1, - "insert", - { - "characters": "C" - }, - "AgAAAJoAAAAAAAAAmwAAAAAAAAAAAAAAmwAAAAAAAACbAAAAAAAAAAEAAABj", - "AQAAAAAAAAABAAAAmwAAAAAAAACaAAAAAAAAAAAAAAAAAPC/" - ], - [ - 284, - 1, - "insert", - { - "characters": " :" - }, - "AgAAABIBAAAAAAAAEwEAAAAAAAAAAAAAEwEAAAAAAAAUAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgEAAAAAAAASAQAAAAAAAAAAAAAAAPC/" - ], - [ - 285, - 1, - "insert", - { - "characters": " Comment" - }, - "CAAAABQBAAAAAAAAFQEAAAAAAAAAAAAAFQEAAAAAAAAWAQAAAAAAAAAAAAAWAQAAAAAAABcBAAAAAAAAAAAAABcBAAAAAAAAGAEAAAAAAAAAAAAAGAEAAAAAAAAZAQAAAAAAAAAAAAAZAQAAAAAAABoBAAAAAAAAAAAAABoBAAAAAAAAGwEAAAAAAAAAAAAAGwEAAAAAAAAcAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAFAEAAAAAAAAUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 289, - 1, - "right_delete", - null, - "AQAAAEkBAAAAAAAASQEAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAATQEAAAAAAABJAQAAAAAAAAAAAAAAAAAA" - ], - [ - 290, - 1, - "left_delete", - null, - "AQAAAEgBAAAAAAAASAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 293, - 1, - "left_delete", - null, - "AQAAAFMBAAAAAAAAUwEAAAAAAAABAAAALg", - "AQAAAAAAAAABAAAAVAEAAAAAAABUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 294, - 1, - "insert", - { - "characters": "." - }, - "AQAAAFMBAAAAAAAAVAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUwEAAAAAAABTAQAAAAAAAAAAAAAAAPC/" - ], - [ - 295, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_gravatar\",\"position\":{\"line\":14,\"character\":46},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_gravatar\",\"sortText\":\"09.9999.author_gravatar\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "author_gravatar" - }, - "AQAAAFQBAAAAAAAAYwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAEAAAAAAABUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 296, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAGMBAAAAAAAAZAEAAAAAAAAAAAAAZAEAAAAAAABlAQAAAAAAAAAAAABlAQAAAAAAAGYBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYwEAAAAAAABjAQAAAAAAAAAAAAAAAPC/" - ], - [ - 297, - 1, - "insert", - { - "characters": " other.a" - }, - "CAAAAGYBAAAAAAAAZwEAAAAAAAAAAAAAZwEAAAAAAABoAQAAAAAAAAAAAABoAQAAAAAAAGkBAAAAAAAAAAAAAGkBAAAAAAAAagEAAAAAAAAAAAAAagEAAAAAAABrAQAAAAAAAAAAAABrAQAAAAAAAGwBAAAAAAAAAAAAAGwBAAAAAAAAbQEAAAAAAAAAAAAAbQEAAAAAAABuAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAZgEAAAAAAABmAQAAAAAAAAAAAAAAAPC/" - ], - [ - 298, - 1, - "insert_completion", - { - "completion": "author_gravatar", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_gravatar" - }, - "AgAAAG0BAAAAAAAAbQEAAAAAAAABAAAAYW0BAAAAAAAAfAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbgEAAAAAAABuAQAAAAAAAAAAAAAAAPC/" - ], - [ - 299, - 1, - "insert", - { - "characters": " and" - }, - "BAAAAHwBAAAAAAAAfQEAAAAAAAAAAAAAfQEAAAAAAAB+AQAAAAAAAAAAAAB+AQAAAAAAAH8BAAAAAAAAAAAAAH8BAAAAAAAAgAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfAEAAAAAAAB8AQAAAAAAAAAAAAAAAPC/" - ], - [ - 300, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAAIABAAAAAAAAgQEAAAAAAAAAAAAAgQEAAAAAAACCAQAAAAAAAAAAAACCAQAAAAAAAIMBAAAAAAAAAAAAAIMBAAAAAAAAhAEAAAAAAAAAAAAAhAEAAAAAAACFAQAAAAAAAAAAAACFAQAAAAAAAIYBAAAAAAAAAAAAAIYBAAAAAAAAhwEAAAAAAAAAAAAAhwEAAAAAAACIAQAAAAAAAAAAAACIAQAAAAAAAIkBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgAEAAAAAAACAAQAAAAAAAAAAAAAAAPC/" - ], - [ - 302, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_name\",\"position\":{\"line\":14,\"character\":99},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_name\",\"sortText\":\"09.9999.author_name\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "author_name" - }, - "AQAAAIkBAAAAAAAAlAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiQEAAAAAAACJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 303, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAJQBAAAAAAAAlQEAAAAAAAAAAAAAlQEAAAAAAACWAQAAAAAAAAAAAACWAQAAAAAAAJcBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlAEAAAAAAACUAQAAAAAAAAAAAAAAAPC/" - ], - [ - 304, - 1, - "insert", - { - "characters": " other." - }, - "BwAAAJcBAAAAAAAAmAEAAAAAAAAAAAAAmAEAAAAAAACZAQAAAAAAAAAAAACZAQAAAAAAAJoBAAAAAAAAAAAAAJoBAAAAAAAAmwEAAAAAAAAAAAAAmwEAAAAAAACcAQAAAAAAAAAAAACcAQAAAAAAAJ0BAAAAAAAAAAAAAJ0BAAAAAAAAngEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAlwEAAAAAAACXAQAAAAAAAAAAAAAAAPC/" - ], - [ - 305, - 1, - "insert", - { - "characters": "a" - }, - "AQAAAJ4BAAAAAAAAnwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAngEAAAAAAACeAQAAAAAAAAAAAAAAAPC/" - ], - [ - 306, - 1, - "insert_completion", - { - "completion": "author_name", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_name" - }, - "AgAAAJ4BAAAAAAAAngEAAAAAAAABAAAAYZ4BAAAAAAAAqQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAnwEAAAAAAACfAQAAAAAAAAAAAAAAAPC/" - ], - [ - 307, - 1, - "insert", - { - "characters": " and" - }, - "BAAAAKkBAAAAAAAAqgEAAAAAAAAAAAAAqgEAAAAAAACrAQAAAAAAAAAAAACrAQAAAAAAAKwBAAAAAAAAAAAAAKwBAAAAAAAArQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqQEAAAAAAACpAQAAAAAAAAAAAAAAAPC/" - ], - [ - 308, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAAK0BAAAAAAAArgEAAAAAAAAAAAAArgEAAAAAAACvAQAAAAAAAAAAAACvAQAAAAAAALABAAAAAAAAAAAAALABAAAAAAAAsQEAAAAAAAAAAAAAsQEAAAAAAACyAQAAAAAAAAAAAACyAQAAAAAAALMBAAAAAAAAAAAAALMBAAAAAAAAtAEAAAAAAAAAAAAAtAEAAAAAAAC1AQAAAAAAAAAAAAC1AQAAAAAAALYBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAArQEAAAAAAACtAQAAAAAAAAAAAAAAAPC/" - ], - [ - 311, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"author_site\",\"position\":{\"line\":14,\"character\":144},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"author_site\",\"sortText\":\"09.9999.author_site\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "author_site" - }, - "AQAAALYBAAAAAAAAwQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtgEAAAAAAAC2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 312, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAMEBAAAAAAAAwgEAAAAAAAAAAAAAwgEAAAAAAADDAQAAAAAAAAAAAADDAQAAAAAAAMQBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQEAAAAAAADBAQAAAAAAAAAAAAAAAPC/" - ], - [ - 313, - 1, - "insert", - { - "characters": " other." - }, - "BwAAAMQBAAAAAAAAxQEAAAAAAAAAAAAAxQEAAAAAAADGAQAAAAAAAAAAAADGAQAAAAAAAMcBAAAAAAAAAAAAAMcBAAAAAAAAyAEAAAAAAAAAAAAAyAEAAAAAAADJAQAAAAAAAAAAAADJAQAAAAAAAMoBAAAAAAAAAAAAAMoBAAAAAAAAywEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxAEAAAAAAADEAQAAAAAAAAAAAAAAAPC/" - ], - [ - 314, - 1, - "insert", - { - "characters": "ai" - }, - "AgAAAMsBAAAAAAAAzAEAAAAAAAAAAAAAzAEAAAAAAADNAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAywEAAAAAAADLAQAAAAAAAAAAAAAAAPC/" - ], - [ - 315, - 1, - "insert_completion", - { - "completion": "author_site", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "author_site" - }, - "AgAAAMsBAAAAAAAAywEAAAAAAAACAAAAYWnLAQAAAAAAANYBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAzQEAAAAAAADNAQAAAAAAAAAAAAAAAPC/" - ], - [ - 316, - 1, - "insert", - { - "characters": " and" - }, - "BAAAANYBAAAAAAAA1wEAAAAAAAAAAAAA1wEAAAAAAADYAQAAAAAAAAAAAADYAQAAAAAAANkBAAAAAAAAAAAAANkBAAAAAAAA2gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1gEAAAAAAADWAQAAAAAAAAAAAAAAAPC/" - ], - [ - 317, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAANoBAAAAAAAA2wEAAAAAAAAAAAAA2wEAAAAAAADcAQAAAAAAAAAAAADcAQAAAAAAAN0BAAAAAAAAAAAAAN0BAAAAAAAA3gEAAAAAAAAAAAAA3gEAAAAAAADfAQAAAAAAAAAAAADfAQAAAAAAAOABAAAAAAAAAAAAAOABAAAAAAAA4QEAAAAAAAAAAAAA4QEAAAAAAADiAQAAAAAAAAAAAADiAQAAAAAAAOMBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gEAAAAAAADaAQAAAAAAAAAAAAAAAPC/" - ], - [ - 323, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"content\",\"position\":{\"line\":14,\"character\":189},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"content\",\"sortText\":\"09.9999.content\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "content" - }, - "AQAAAOMBAAAAAAAA6gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4wEAAAAAAADjAQAAAAAAAAAAAAAAAPC/" - ], - [ - 324, - 1, - "insert", - { - "characters": " =" - }, - "AgAAAOoBAAAAAAAA6wEAAAAAAAAAAAAA6wEAAAAAAADsAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6gEAAAAAAADqAQAAAAAAAAAAAAAAAPC/" - ], - [ - 325, - 1, - "insert", - { - "characters": "=" - }, - "AQAAAOwBAAAAAAAA7QEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7AEAAAAAAADsAQAAAAAAAAAAAAAAAPC/" - ], - [ - 326, - 1, - "insert", - { - "characters": " other." - }, - "BwAAAO0BAAAAAAAA7gEAAAAAAAAAAAAA7gEAAAAAAADvAQAAAAAAAAAAAADvAQAAAAAAAPABAAAAAAAAAAAAAPABAAAAAAAA8QEAAAAAAAAAAAAA8QEAAAAAAADyAQAAAAAAAAAAAADyAQAAAAAAAPMBAAAAAAAAAAAAAPMBAAAAAAAA9AEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA7QEAAAAAAADtAQAAAAAAAAAAAAAAAPC/" - ], - [ - 327, - 1, - "insert", - { - "characters": "content" - }, - "BwAAAPQBAAAAAAAA9QEAAAAAAAAAAAAA9QEAAAAAAAD2AQAAAAAAAAAAAAD2AQAAAAAAAPcBAAAAAAAAAAAAAPcBAAAAAAAA+AEAAAAAAAAAAAAA+AEAAAAAAAD5AQAAAAAAAAAAAAD5AQAAAAAAAPoBAAAAAAAAAAAAAPoBAAAAAAAA+wEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA9AEAAAAAAAD0AQAAAAAAAAAAAAAAAPC/" - ], - [ - 328, - 1, - "insert", - { - "characters": " and" - }, - "BAAAAPsBAAAAAAAA/AEAAAAAAAAAAAAA/AEAAAAAAAD9AQAAAAAAAAAAAAD9AQAAAAAAAP4BAAAAAAAAAAAAAP4BAAAAAAAA/wEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA+wEAAAAAAAD7AQAAAAAAAAAAAAAAAPC/" - ], - [ - 329, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAAP8BAAAAAAAAAAIAAAAAAAAAAAAAAAIAAAAAAAABAgAAAAAAAAAAAAABAgAAAAAAAAICAAAAAAAAAAAAAAICAAAAAAAAAwIAAAAAAAAAAAAAAwIAAAAAAAAEAgAAAAAAAAAAAAAEAgAAAAAAAAUCAAAAAAAAAAAAAAUCAAAAAAAABgIAAAAAAAAAAAAABgIAAAAAAAAHAgAAAAAAAAAAAAAHAgAAAAAAAAgCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA/wEAAAAAAAD/AQAAAAAAAAAAAAAAAPC/" - ], - [ - 334, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"created\",\"position\":{\"line\":14,\"character\":226},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"created\",\"sortText\":\"09.9999.created\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "created" - }, - "AQAAAAgCAAAAAAAADwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACAIAAAAAAAAIAgAAAAAAAAAAAAAAAPC/" - ], - [ - 335, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAA8CAAAAAAAAEAIAAAAAAAAAAAAAEAIAAAAAAAARAgAAAAAAAAAAAAARAgAAAAAAABICAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADwIAAAAAAAAPAgAAAAAAAAAAAAAAAPC/" - ], - [ - 336, - 1, - "insert", - { - "characters": " other.c" - }, - "CAAAABICAAAAAAAAEwIAAAAAAAAAAAAAEwIAAAAAAAAUAgAAAAAAAAAAAAAUAgAAAAAAABUCAAAAAAAAAAAAABUCAAAAAAAAFgIAAAAAAAAAAAAAFgIAAAAAAAAXAgAAAAAAAAAAAAAXAgAAAAAAABgCAAAAAAAAAAAAABgCAAAAAAAAGQIAAAAAAAAAAAAAGQIAAAAAAAAaAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEgIAAAAAAAASAgAAAAAAAAAAAAAAAPC/" - ], - [ - 337, - 1, - "insert", - { - "characters": "re" - }, - "AgAAABoCAAAAAAAAGwIAAAAAAAAAAAAAGwIAAAAAAAAcAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAGgIAAAAAAAAaAgAAAAAAAAAAAAAAAPC/" - ], - [ - 338, - 1, - "insert_completion", - { - "completion": "created", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "created" - }, - "AgAAABkCAAAAAAAAGQIAAAAAAAADAAAAY3JlGQIAAAAAAAAgAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAHAIAAAAAAAAcAgAAAAAAAAAAAAAAAPC/" - ], - [ - 339, - 1, - "insert", - { - "characters": " and" - }, - "BAAAACACAAAAAAAAIQIAAAAAAAAAAAAAIQIAAAAAAAAiAgAAAAAAAAAAAAAiAgAAAAAAACMCAAAAAAAAAAAAACMCAAAAAAAAJAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAIAIAAAAAAAAgAgAAAAAAAAAAAAAAAPC/" - ], - [ - 340, - 1, - "insert", - { - "characters": " comment." - }, - "CQAAACQCAAAAAAAAJQIAAAAAAAAAAAAAJQIAAAAAAAAmAgAAAAAAAAAAAAAmAgAAAAAAACcCAAAAAAAAAAAAACcCAAAAAAAAKAIAAAAAAAAAAAAAKAIAAAAAAAApAgAAAAAAAAAAAAApAgAAAAAAACoCAAAAAAAAAAAAACoCAAAAAAAAKwIAAAAAAAAAAAAAKwIAAAAAAAAsAgAAAAAAAAAAAAAsAgAAAAAAAC0CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJAIAAAAAAAAkAgAAAAAAAAAAAAAAAPC/" - ], - [ - 347, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"notified\",\"position\":{\"line\":14,\"character\":263},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"notified\",\"sortText\":\"09.9999.notified\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": true, - "trigger": "notified" - }, - "AQAAAC0CAAAAAAAANQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALQIAAAAAAAAtAgAAAAAAAAAAAAAAAPC/" - ], - [ - 348, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAADUCAAAAAAAANgIAAAAAAAAAAAAANgIAAAAAAAA3AgAAAAAAAAAAAAA3AgAAAAAAADgCAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAANQIAAAAAAAA1AgAAAAAAAAAAAAAAAPC/" - ], - [ - 349, - 1, - "insert", - { - "characters": " other" - }, - "BgAAADgCAAAAAAAAOQIAAAAAAAAAAAAAOQIAAAAAAAA6AgAAAAAAAAAAAAA6AgAAAAAAADsCAAAAAAAAAAAAADsCAAAAAAAAPAIAAAAAAAAAAAAAPAIAAAAAAAA9AgAAAAAAAAAAAAA9AgAAAAAAAD4CAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAOAIAAAAAAAA4AgAAAAAAAAAAAAAAAPC/" - ], - [ - 350, - 1, - "insert", - { - "characters": ".noti" - }, - "BQAAAD4CAAAAAAAAPwIAAAAAAAAAAAAAPwIAAAAAAABAAgAAAAAAAAAAAABAAgAAAAAAAEECAAAAAAAAAAAAAEECAAAAAAAAQgIAAAAAAAAAAAAAQgIAAAAAAABDAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAPgIAAAAAAAA+AgAAAAAAAAAAAAAAAPC/" - ], - [ - 351, - 1, - "insert_completion", - { - "completion": "notified", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "notified" - }, - "AgAAAD8CAAAAAAAAPwIAAAAAAAAEAAAAbm90aT8CAAAAAAAARwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAQwIAAAAAAABDAgAAAAAAAAAAAAAAAPC/" - ], - [ - 352, - 1, - "insert", - { - "characters": " and" - }, - "BAAAAEcCAAAAAAAASAIAAAAAAAAAAAAASAIAAAAAAABJAgAAAAAAAAAAAABJAgAAAAAAAEoCAAAAAAAAAAAAAEoCAAAAAAAASwIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAARwIAAAAAAABHAgAAAAAAAAAAAAAAAPC/" - ], - [ - 353, - 1, - "insert", - { - "characters": " comment" - }, - "CAAAAEsCAAAAAAAATAIAAAAAAAAAAAAATAIAAAAAAABNAgAAAAAAAAAAAABNAgAAAAAAAE4CAAAAAAAAAAAAAE4CAAAAAAAATwIAAAAAAAAAAAAATwIAAAAAAABQAgAAAAAAAAAAAABQAgAAAAAAAFECAAAAAAAAAAAAAFECAAAAAAAAUgIAAAAAAAAAAAAAUgIAAAAAAABTAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASwIAAAAAAABLAgAAAAAAAAAAAAAAAPC/" - ], - [ - 354, - 1, - "insert", - { - "characters": ".p" - }, - "AgAAAFMCAAAAAAAAVAIAAAAAAAAAAAAAVAIAAAAAAABVAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUwIAAAAAAABTAgAAAAAAAAAAAAAAAPC/" - ], - [ - 355, - 1, - "insert_completion", - { - "completion": "lsp_select_completion_item {\"item\":{\"data\":{\"workspacePath\":\"/home/yannic/work/stacosys\",\"symbolLabel\":\"published\",\"position\":{\"line\":14,\"character\":303},\"filePath\":\"/home/yannic/work/stacosys/tests/test_db.py\"},\"label\":\"published\",\"sortText\":\"09.9999.published\",\"kind\":6},\"session_name\":\"LSP-pyright\"}", - "format": "command", - "keep_prefix": false, - "must_insert": false, - "trigger": "published" - }, - "AgAAAFQCAAAAAAAAVAIAAAAAAAABAAAAcFQCAAAAAAAAXQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVQIAAAAAAABVAgAAAAAAAAAAAAAAAPC/" - ], - [ - 356, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAF0CAAAAAAAAXgIAAAAAAAAAAAAAXgIAAAAAAABfAgAAAAAAAAAAAABfAgAAAAAAAGACAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXQIAAAAAAABdAgAAAAAAAAAAAAAAAPC/" - ], - [ - 357, - 1, - "insert", - { - "characters": " other.puy" - }, - "CgAAAGACAAAAAAAAYQIAAAAAAAAAAAAAYQIAAAAAAABiAgAAAAAAAAAAAABiAgAAAAAAAGMCAAAAAAAAAAAAAGMCAAAAAAAAZAIAAAAAAAAAAAAAZAIAAAAAAABlAgAAAAAAAAAAAABlAgAAAAAAAGYCAAAAAAAAAAAAAGYCAAAAAAAAZwIAAAAAAAAAAAAAZwIAAAAAAABoAgAAAAAAAAAAAABoAgAAAAAAAGkCAAAAAAAAAAAAAGkCAAAAAAAAagIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYAIAAAAAAABgAgAAAAAAAAAAAAAAAPC/" - ], - [ - 358, - 1, - "left_delete", - null, - "AQAAAGkCAAAAAAAAaQIAAAAAAAABAAAAeQ", - "AQAAAAAAAAABAAAAagIAAAAAAABqAgAAAAAAAAAAAAAAAPC/" - ], - [ - 359, - 1, - "insert_completion", - { - "completion": "published", - "format": "text", - "keep_prefix": false, - "must_insert": false, - "trigger": "published" - }, - "AgAAAGcCAAAAAAAAZwIAAAAAAAACAAAAcHVnAgAAAAAAAHACAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAaQIAAAAAAABpAgAAAAAAAAAAAAAAAPC/" - ], - [ - 362, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAHACAAAAAAAAcQIAAAAAAAAAAAAAcQIAAAAAAAB1AgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAcAIAAAAAAABwAgAAAAAAAP///////+9/" - ], - [ - 364, - 1, - "trim_trailing_white_space", - null, - "AQAAAHECAAAAAAAAcQIAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAdQIAAAAAAAB1AgAAAAAAAAAAAAAAAPC/" - ], - [ - 378, - 1, - "insert", - { - "characters": "equals_" - }, - "BwAAAG8DAAAAAAAAcAMAAAAAAAAAAAAAcAMAAAAAAABxAwAAAAAAAAAAAABxAwAAAAAAAHIDAAAAAAAAAAAAAHIDAAAAAAAAcwMAAAAAAAAAAAAAcwMAAAAAAAB0AwAAAAAAAAAAAAB0AwAAAAAAAHUDAAAAAAAAAAAAAHUDAAAAAAAAdgMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAbwMAAAAAAABvAwAAAAAAAAAAAAAAAPC/" - ], - [ - 379, - 1, - "left_delete", - null, - "AQAAAHUDAAAAAAAAdQMAAAAAAAABAAAAXw", - "AQAAAAAAAAABAAAAdgMAAAAAAAB2AwAAAAAAAAAAAAAAAPC/" - ], - [ - 380, - 1, - "insert", - { - "characters": "_comment(" - }, - "CQAAAHUDAAAAAAAAdgMAAAAAAAAAAAAAdgMAAAAAAAB3AwAAAAAAAAAAAAB3AwAAAAAAAHgDAAAAAAAAAAAAAHgDAAAAAAAAeQMAAAAAAAAAAAAAeQMAAAAAAAB6AwAAAAAAAAAAAAB6AwAAAAAAAHsDAAAAAAAAAAAAAHsDAAAAAAAAfAMAAAAAAAAAAAAAfAMAAAAAAAB9AwAAAAAAAAAAAAB9AwAAAAAAAH4DAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAdQMAAAAAAAB1AwAAAAAAAAAAAAAAAPC/" - ], - [ - 381, - 1, - "insert", - { - "characters": "c1," - }, - "AwAAAH4DAAAAAAAAfwMAAAAAAAAAAAAAfwMAAAAAAACAAwAAAAAAAAAAAACAAwAAAAAAAIEDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAfgMAAAAAAAB+AwAAAAAAAAAAAAAAAPC/" - ], - [ - 382, - 1, - "insert", - { - "characters": " " - }, - "AQAAAIEDAAAAAAAAggMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAgQMAAAAAAACBAwAAAAAAAAAAAAAAAPC/" - ], - [ - 385, - 1, - "insert", - { - "characters": ")" - }, - "AgAAAIkDAAAAAAAAigMAAAAAAAAAAAAAigMAAAAAAACKAwAAAAAAAAwAAAAuaWQgPT0gYzEuaWQ", - "AQAAAAAAAAABAAAAiQMAAAAAAACVAwAAAAAAAAAAAAAAAPC/" - ], - [ - 414, - 1, - "insert", - { - "characters": "\n\t" - }, - "AwAAAEgBAAAAAAAASQEAAAAAAAAAAAAASQEAAAAAAABNAQAAAAAAAAAAAABNAQAAAAAAAFEBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 417, - 1, - "right_delete", - null, - "AQAAAEkBAAAAAAAASQEAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAUQEAAAAAAABJAQAAAAAAAAAAAAAAAAAA" - ], - [ - 418, - 1, - "left_delete", - null, - "AQAAAEgBAAAAAAAASAEAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 420, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAEgBAAAAAAAASQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 421, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 422, - 1, - "insert", - { - "characters": "\t" - }, - "AQAAAE4BAAAAAAAAUgEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAATgEAAAAAAABOAQAAAAAAAAAAAAAAAPC/" - ], - [ - 424, - 1, - "right_delete", - null, - "AQAAAEoBAAAAAAAASgEAAAAAAAAIAAAAICAgICAgICA", - "AQAAAAAAAAABAAAAUgEAAAAAAABKAQAAAAAAAAAAAAAAAAAA" - ], - [ - 425, - 2, - "left_delete", - null, - "AgAAAEkBAAAAAAAASQEAAAAAAAABAAAACkgBAAAAAAAASAEAAAAAAAABAAAAXA", - "AQAAAAAAAAABAAAASgEAAAAAAABKAQAAAAAAAAAAAAAAAPC/" - ], - [ - 434, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAEgBAAAAAAAASQEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASAEAAAAAAABIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 435, - 1, - "insert", - { - "characters": "\n\t" - }, - "AwAAAEkBAAAAAAAASgEAAAAAAAAAAAAASgEAAAAAAABOAQAAAAAAAAAAAABOAQAAAAAAAFIBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAASQEAAAAAAABJAQAAAAAAAAAAAAAAAPC/" - ], - [ - 438, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAIcBAAAAAAAAiAEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhwEAAAAAAACHAQAAAAAAAAAAAAAAAPC/" - ], - [ - 439, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAIgBAAAAAAAAiQEAAAAAAAAAAAAAiQEAAAAAAACRAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAiAEAAAAAAACIAQAAAAAAAAAAAAAAAPC/" - ], - [ - 442, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAL4BAAAAAAAAvwEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvgEAAAAAAAC+AQAAAAAAAAAAAAAAAPC/" - ], - [ - 443, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAL8BAAAAAAAAwAEAAAAAAAAAAAAAwAEAAAAAAADIAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvwEAAAAAAAC/AQAAAAAAAAAAAAAAAPC/" - ], - [ - 446, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAPUBAAAAAAAA9gEAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA9QEAAAAAAAD1AQAAAAAAAAAAAAAAAPC/" - ], - [ - 447, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAPYBAAAAAAAA9wEAAAAAAAAAAAAA9wEAAAAAAAD/AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA9gEAAAAAAAD2AQAAAAAAAAAAAAAAAPC/" - ], - [ - 450, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAACQCAAAAAAAAJQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJAIAAAAAAAAkAgAAAAAAAAAAAAAAAPC/" - ], - [ - 451, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAACUCAAAAAAAAJgIAAAAAAAAAAAAAJgIAAAAAAAAuAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJQIAAAAAAAAlAgAAAAAAAAAAAAAAAPC/" - ], - [ - 454, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAFMCAAAAAAAAVAIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAUwIAAAAAAABTAgAAAAAAAAAAAAAAAPC/" - ], - [ - 455, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAFQCAAAAAAAAVQIAAAAAAAAAAAAAVQIAAAAAAABdAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAVAIAAAAAAABUAgAAAAAAAAAAAAAAAPC/" - ], - [ - 458, - 1, - "insert", - { - "characters": "\\" - }, - "AQAAAIQCAAAAAAAAhQIAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhAIAAAAAAACEAgAAAAAAAAAAAAAAAPC/" - ], - [ - 459, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAIUCAAAAAAAAhgIAAAAAAAAAAAAAhgIAAAAAAACOAgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhQIAAAAAAACFAgAAAAAAAAAAAAAAAPC/" - ], - [ - 493, - 1, - "insert", - { - "characters": "\nass" - }, - "BQAAALQEAAAAAAAAtQQAAAAAAAAAAAAAtQQAAAAAAAC5BAAAAAAAAAAAAAC5BAAAAAAAALoEAAAAAAAAAAAAALoEAAAAAAAAuwQAAAAAAAAAAAAAuwQAAAAAAAC8BAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAtAQAAAAAAAC0BAAAAAAAAAAAAAAAAPC/" - ], - [ - 494, - 1, - "insert", - { - "characters": "ert" - }, - "AwAAALwEAAAAAAAAvQQAAAAAAAAAAAAAvQQAAAAAAAC+BAAAAAAAAAAAAAC+BAAAAAAAAL8EAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvAQAAAAAAAC8BAAAAAAAAAAAAAAAAPC/" - ], - [ - 495, - 1, - "insert", - { - "characters": " 1" - }, - "AgAAAL8EAAAAAAAAwAQAAAAAAAAAAAAAwAQAAAAAAADBBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAvwQAAAAAAAC/BAAAAAAAAAAAAAAAAPC/" - ], - [ - 496, - 1, - "insert", - { - "characters": " ==" - }, - "AwAAAMEEAAAAAAAAwgQAAAAAAAAAAAAAwgQAAAAAAADDBAAAAAAAAAAAAADDBAAAAAAAAMQEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAwQQAAAAAAADBBAAAAAAAAAAAAAAAAPC/" - ], - [ - 497, - 1, - "insert", - { - "characters": " " - }, - "AQAAAMQEAAAAAAAAxQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxAQAAAAAAADEBAAAAAAAAAAAAAAAAPC/" - ], - [ - 498, - 1, - "paste", - null, - "AQAAAMUEAAAAAAAA4AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" - ], - [ - 502, - 1, - "insert", - { - "characters": "dao." - }, - "BAAAAMUEAAAAAAAAxgQAAAAAAAAAAAAAxgQAAAAAAADHBAAAAAAAAAAAAADHBAAAAAAAAMgEAAAAAAAAAAAAAMgEAAAAAAAAyQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" - ], - [ - 504, - 1, - "insert_snippet", - { - "contents": "($0)" - }, - "AQAAAOQEAAAAAAAA5gQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5AQAAAAAAADkBAAAAAAAAP///////+9/" - ], - [ - 505, - 1, - "insert_snippet", - { - "contents": "\"$0\"" - }, - "AQAAAOUEAAAAAAAA5wQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA5QQAAAAAAADlBAAAAAAAAAAAAAAAAPC/" - ], - [ - 509, - 2, - "left_delete", - null, - "AgAAAOYEAAAAAAAA5gQAAAAAAAABAAAAIuUEAAAAAAAA5QQAAAAAAAABAAAAIg", - "AQAAAAAAAAABAAAA5wQAAAAAAADnBAAAAAAAAAAAAAAAAPC/" - ], - [ - 514, - 1, - "insert", - { - "characters": "len(" - }, - "BAAAAMUEAAAAAAAAxgQAAAAAAAAAAAAAxgQAAAAAAADHBAAAAAAAAAAAAADHBAAAAAAAAMgEAAAAAAAAAAAAAMgEAAAAAAAAyQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAxQQAAAAAAADFBAAAAAAAAAAAAAAAAPC/" - ], - [ - 516, - 1, - "insert", - { - "characters": ")" - }, - "AQAAAOoEAAAAAAAA6wQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA6gQAAAAAAADqBAAAAAAAAP///////+9/" - ], - [ - 523, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAACUGAAAAAAAAJgYAAAAAAAAAAAAAJgYAAAAAAAAqBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJQYAAAAAAAAlBgAAAAAAAAAAAAAAAPC/" - ], - [ - 524, - 1, - "insert", - { - "characters": "\n" - }, - "AwAAACoGAAAAAAAAKwYAAAAAAAAAAAAAKwYAAAAAAAAvBgAAAAAAAAAAAAAmBgAAAAAAACYGAAAAAAAABAAAACAgICA", - "AQAAAAAAAAABAAAAKgYAAAAAAAAqBgAAAAAAAAAAAAAAAPC/" - ], - [ - 525, - 1, - "insert", - { - "characters": "#" - }, - "AQAAACsGAAAAAAAALAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKwYAAAAAAAArBgAAAAAAAAAAAAAAAPC/" - ], - [ - 526, - 1, - "insert", - { - "characters": " count" - }, - "BgAAACwGAAAAAAAALQYAAAAAAAAAAAAALQYAAAAAAAAuBgAAAAAAAAAAAAAuBgAAAAAAAC8GAAAAAAAAAAAAAC8GAAAAAAAAMAYAAAAAAAAAAAAAMAYAAAAAAAAxBgAAAAAAAAAAAAAxBgAAAAAAADIGAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALAYAAAAAAAAsBgAAAAAAAAAAAAAAAPC/" - ], - [ - 527, - 1, - "insert", - { - "characters": " published" - }, - "CgAAADIGAAAAAAAAMwYAAAAAAAAAAAAAMwYAAAAAAAA0BgAAAAAAAAAAAAA0BgAAAAAAADUGAAAAAAAAAAAAADUGAAAAAAAANgYAAAAAAAAAAAAANgYAAAAAAAA3BgAAAAAAAAAAAAA3BgAAAAAAADgGAAAAAAAAAAAAADgGAAAAAAAAOQYAAAAAAAAAAAAAOQYAAAAAAAA6BgAAAAAAAAAAAAA6BgAAAAAAADsGAAAAAAAAAAAAADsGAAAAAAAAPAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAMgYAAAAAAAAyBgAAAAAAAAAAAAAAAPC/" - ], - [ - 536, - 1, - "paste", - null, - "AQAAAKsGAAAAAAAA3QYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAYG5A" - ], - [ - 538, - 1, - "insert", - { - "characters": "\t" - }, - "AQAAAKsGAAAAAAAArwYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAAAA" - ], - [ - 542, - 1, - "insert", - { - "characters": "3" - }, - "AgAAALYGAAAAAAAAtwYAAAAAAAAAAAAAtwYAAAAAAAC3BgAAAAAAAAEAAAAx", - "AQAAAAAAAAABAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" - ], - [ - 544, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAOEGAAAAAAAA4gYAAAAAAAAAAAAA4gYAAAAAAADmBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4QYAAAAAAADhBgAAAAAAAP///////+9/" - ], - [ - 549, - 1, - "insert", - { - "characters": "0" - }, - "AwAAALYGAAAAAAAAtwYAAAAAAAAAAAAAtwYAAAAAAAC3BgAAAAAAAAEAAAAz4gYAAAAAAADiBgAAAAAAAAQAAAAgICAg", - "AQAAAAAAAAABAAAAtgYAAAAAAAC3BgAAAAAAAAAAAAAAAPC/" - ], - [ - 554, - 1, - "cut", - null, - "AQAAAK8GAAAAAAAArwYAAAAAAAAyAAAAYXNzZXJ0IDAgPT0gbGVuKGRhby5maW5kX25vdF9wdWJsaXNoZWRfY29tbWVudHMoKSk", - "AQAAAAAAAAABAAAArwYAAAAAAADhBgAAAAAAAP///////+9/" - ], - [ - 556, - 1, - "left_delete", - null, - "AQAAAKoGAAAAAAAAqgYAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAqwYAAAAAAACrBgAAAAAAAAAAAAAAAAAA" - ], - [ - 558, - 1, - "paste", - null, - "AQAAACYGAAAAAAAAWAYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJgYAAAAAAAAmBgAAAAAAAAAAAAAAYH5A" - ], - [ - 560, - 1, - "insert", - { - "characters": "\t" - }, - "AQAAACYGAAAAAAAAKgYAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAJgYAAAAAAAAmBgAAAAAAAAAAAAAAAAAA" - ], - [ - 562, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAFwGAAAAAAAAXQYAAAAAAAAAAAAAXQYAAAAAAABhBgAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAXAYAAAAAAABcBgAAAAAAAP///////+9/" - ], - [ - 564, - 1, - "trim_trailing_white_space", - null, - "AgAAAOUGAAAAAAAA5QYAAAAAAAAEAAAAICAgIF0GAAAAAAAAXQYAAAAAAAAEAAAAICAgIA", - "AQAAAAAAAAABAAAAYQYAAAAAAABhBgAAAAAAAAAAAAAAAPC/" - ], - [ - 571, - 2, - "right_delete", - null, - "AgAAAOkGAAAAAAAA6QYAAAAAAAAEAAAAdGVzdOkGAAAAAAAA6QYAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAA6QYAAAAAAADtBgAAAAAAAAAAAAAAAPC/" - ], - [ - 583, - 1, - "reindent", - null, - "AQAAANEDAAAAAAAA1QMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0QMAAAAAAADRAwAAAAAAAAAAAAAAAPC/" - ], - [ - 584, - 1, - "paste", - null, - "AQAAANUDAAAAAAAA6wMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1QMAAAAAAADVAwAAAAAAAAAAAAAAAPC/" - ], - [ - 597, - 1, - "insert", - { - "characters": "\nc1" - }, - "BAAAANADAAAAAAAA0QMAAAAAAAAAAAAA0QMAAAAAAADVAwAAAAAAAAAAAADVAwAAAAAAANYDAAAAAAAAAAAAANYDAAAAAAAA1wMAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA0AMAAAAAAADQAwAAAAAAAP///////+9/" - ], - [ - 598, - 1, - "insert", - { - "characters": ".id" - }, - "AwAAANcDAAAAAAAA2AMAAAAAAAAAAAAA2AMAAAAAAADZAwAAAAAAAAAAAADZAwAAAAAAANoDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA1wMAAAAAAADXAwAAAAAAAAAAAAAAAPC/" - ], - [ - 599, - 1, - "insert", - { - "characters": " =" - }, - "AgAAANoDAAAAAAAA2wMAAAAAAAAAAAAA2wMAAAAAAADcAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA2gMAAAAAAADaAwAAAAAAAAAAAAAAAPC/" - ], - [ - 600, - 1, - "insert", - { - "characters": " find" - }, - "BQAAANwDAAAAAAAA3QMAAAAAAAAAAAAA3QMAAAAAAADeAwAAAAAAAAAAAADeAwAAAAAAAN8DAAAAAAAAAAAAAN8DAAAAAAAA4AMAAAAAAAAAAAAA4AMAAAAAAADhAwAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA3AMAAAAAAADcAwAAAAAAAAAAAAAAAPC/" - ], - [ - 601, - 1, - "insert", - { - "characters": "_c1.id" - }, - "BgAAAOEDAAAAAAAA4gMAAAAAAAAAAAAA4gMAAAAAAADjAwAAAAAAAAAAAADjAwAAAAAAAOQDAAAAAAAAAAAAAOQDAAAAAAAA5QMAAAAAAAAAAAAA5QMAAAAAAADmAwAAAAAAAAAAAADmAwAAAAAAAOcDAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAA4QMAAAAAAADhAwAAAAAAAAAAAAAAAPC/" - ], - [ - 604, - 1, - "insert", - { - "characters": "\n" - }, - "AgAAAAIEAAAAAAAAAwQAAAAAAAAAAAAAAwQAAAAAAAAHBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAgQAAAAAAAACBAAAAAAAAAAAAAAAAPC/" - ], - [ - 609, - 1, - "paste", - null, - "AQAAAAcEAAAAAAAALgQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABwQAAAAAAAAHBAAAAAAAAAAAAAAAQGRA" - ], - [ - 613, - 1, - "insert", - { - "characters": "assre" - }, - "BgAAAAcEAAAAAAAACAQAAAAAAAAAAAAACAQAAAAAAAAIBAAAAAAAAAkAAABmaW5kX2MxID0IBAAAAAAAAAkEAAAAAAAAAAAAAAkEAAAAAAAACgQAAAAAAAAAAAAACgQAAAAAAAALBAAAAAAAAAAAAAALBAAAAAAAAAwEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAABwQAAAAAAAAQBAAAAAAAAAAAAAAAAPC/" - ], - [ - 614, - 1, - "left_delete", - null, - "AQAAAAsEAAAAAAAACwQAAAAAAAABAAAAZQ", - "AQAAAAAAAAABAAAADAQAAAAAAAAMBAAAAAAAAAAAAAAAAPC/" - ], - [ - 615, - 1, - "insert", - { - "characters": "e" - }, - "AQAAAAsEAAAAAAAADAQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACwQAAAAAAAALBAAAAAAAAAAAAAAAAPC/" - ], - [ - 616, - 2, - "left_delete", - null, - "AgAAAAsEAAAAAAAACwQAAAAAAAABAAAAZQoEAAAAAAAACgQAAAAAAAABAAAAcg", - "AQAAAAAAAAABAAAADAQAAAAAAAAMBAAAAAAAAAAAAAAAAPC/" - ], - [ - 617, - 1, - "insert", - { - "characters": "ert" - }, - "AwAAAAoEAAAAAAAACwQAAAAAAAAAAAAACwQAAAAAAAAMBAAAAAAAAAAAAAAMBAAAAAAAAA0EAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAACgQAAAAAAAAKBAAAAAAAAAAAAAAAAPC/" - ], - [ - 619, - 1, - "insert", - { - "characters": " is" - }, - "AwAAACsEAAAAAAAALAQAAAAAAAAAAAAALAQAAAAAAAAtBAAAAAAAAAAAAAAtBAAAAAAAAC4EAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAKwQAAAAAAAArBAAAAAAAAP///////+9/" - ], - [ - 620, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAC4EAAAAAAAALwQAAAAAAAAAAAAALwQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADEEAAAAAAAAAAAAADEEAAAAAAAAMgQAAAAAAAAAAAAAMgQAAAAAAAAzBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALgQAAAAAAAAuBAAAAAAAAAAAAAAAAPC/" - ], - [ - 622, - 1, - "right_delete", - null, - "AQAAACwEAAAAAAAALAQAAAAAAAAHAAAAaXMgTm9uZQ", - "AQAAAAAAAAABAAAAMwQAAAAAAAAsBAAAAAAAAAAAAAAAAPC/" - ], - [ - 624, - 1, - "insert", - { - "characters": "not" - }, - "AwAAAA4EAAAAAAAADwQAAAAAAAAAAAAADwQAAAAAAAAQBAAAAAAAAAAAAAAQBAAAAAAAABEEAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAADgQAAAAAAAAOBAAAAAAAAAAAAAAAAPC/" - ], - [ - 625, - 1, - "insert", - { - "characters": " " - }, - "AQAAABEEAAAAAAAAEgQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAEQQAAAAAAAARBAAAAAAAAAAAAAAAAPC/" - ], - [ - 627, - 1, - "right_delete", - null, - "AQAAAA4EAAAAAAAADgQAAAAAAAAEAAAAbm90IA", - "AQAAAAAAAAABAAAAEgQAAAAAAAAOBAAAAAAAAAAAAAAAAPC/" - ], - [ - 629, - 1, - "insert", - { - "characters": " " - }, - "AQAAACwEAAAAAAAALQQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALAQAAAAAAAAsBAAAAAAAAP///////+9/" - ], - [ - 630, - 1, - "left_delete", - null, - "AQAAACwEAAAAAAAALAQAAAAAAAABAAAAIA", - "AQAAAAAAAAABAAAALQQAAAAAAAAtBAAAAAAAAAAAAAAAAPC/" - ], - [ - 631, - 1, - "insert", - { - "characters": "is" - }, - "AgAAACwEAAAAAAAALQQAAAAAAAAAAAAALQQAAAAAAAAuBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALAQAAAAAAAAsBAAAAAAAAAAAAAAAAPC/" - ], - [ - 632, - 1, - "insert", - { - "characters": " None" - }, - "BQAAAC4EAAAAAAAALwQAAAAAAAAAAAAALwQAAAAAAAAwBAAAAAAAAAAAAAAwBAAAAAAAADEEAAAAAAAAAAAAADEEAAAAAAAAMgQAAAAAAAAAAAAAMgQAAAAAAAAzBAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAALgQAAAAAAAAuBAAAAAAAAAAAAAAAAPC/" - ], - [ - 638, - 2, - "left_delete", - null, - "AgAAABwIAAAAAAAAHAgAAAAAAABcAAAAICAgIGRhby5kZWxldGVfY29tbWVudChjMSkKICAgIGFzc2VydCAwID09IGxlbihkYW8uZmluZF9wdWJsaXNoZWRfY29tbWVudHNfYnlfdXJsKCIvcG9zdDEiKSkbCAAAAAAAABsIAAAAAAAAAQAAAAo", - "AQAAAAAAAAABAAAAeAgAAAAAAAAcCAAAAAAAAAAAAAAAAPC/" - ], - [ - 641, - 1, - "left_delete", - null, - "AQAAABoIAAAAAAAAGggAAAAAAAABAAAACg", - "AQAAAAAAAAABAAAAGwgAAAAAAAAbCAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "contents": "Package Control Messages\n========================\n\npython-black\n------------\n\n Thanks for trying out the python-black package!\n ===============================================\n\n\n `python-black` is an unofficial `black` plugin developed for Sublime Text 4.\n\n The current version can format the currently active `.py` file before saving.\n\n The configuration file used is the same as the official `black`.\n\n **Sublime Text needs to be restarted after installing `python-black`!**", - "settings": - { - "buffer_size": 506, - "line_ending": "Unix", - "name": "Package Control Messages", - "read_only": true, - "scratch": true - }, - "undo_stack": - [ - [ - 1, - 1, - "insert", - { - "characters": "Package Control Messages\n========================\n\npython-black\n------------\n\n Thanks for trying out the python-black package!\n ===============================================\n\n\n `python-black` is an unofficial `black` plugin developed for Sublime Text 4.\n\n The current version can format the currently active `.py` file before saving.\n\n The configuration file used is the same as the official `black`.\n\n **Sublime Text needs to be restarted after installing `python-black`!**" - }, - "GgAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAAZAAAAAAAAAAAAAAAZAAAAAAAAADEAAAAAAAAAAAAAADEAAAAAAAAAMgAAAAAAAAAAAAAAMgAAAAAAAAAzAAAAAAAAAAAAAAAzAAAAAAAAAD8AAAAAAAAAAAAAAD8AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAABMAAAAAAAAAAAAAABMAAAAAAAAAE0AAAAAAAAAAAAAAE0AAAAAAAAATgAAAAAAAAAAAAAATgAAAAAAAACLAAAAAAAAAAAAAACLAAAAAAAAAIwAAAAAAAAAAAAAAIwAAAAAAAAAyQAAAAAAAAAAAAAAyQAAAAAAAADKAAAAAAAAAAAAAADKAAAAAAAAAMsAAAAAAAAAAAAAAMsAAAAAAAAAzAAAAAAAAAAAAAAAzAAAAAAAAAAaAQAAAAAAAAAAAAAaAQAAAAAAABsBAAAAAAAAAAAAABsBAAAAAAAAHAEAAAAAAAAAAAAAHAEAAAAAAABrAQAAAAAAAAAAAABrAQAAAAAAAGwBAAAAAAAAAAAAAGwBAAAAAAAAbQEAAAAAAAAAAAAAbQEAAAAAAACvAQAAAAAAAAAAAACvAQAAAAAAALABAAAAAAAAAAAAALABAAAAAAAAsQEAAAAAAAAAAAAAsQEAAAAAAAD6AQAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/" - ] - ] - }, - { - "file": "Makefile", - "settings": - { - "buffer_size": 338, - "encoding": "UTF-8", - "line_ending": "Unix" - }, - "undo_stack": - [ - [ - 8, - 1, - "insert", - { - "characters": " tests/" - }, - "BwAAAGEAAAAAAAAAYgAAAAAAAAAAAAAAYgAAAAAAAABjAAAAAAAAAAAAAABjAAAAAAAAAGQAAAAAAAAAAAAAAGQAAAAAAAAAZQAAAAAAAAAAAAAAZQAAAAAAAABmAAAAAAAAAAAAAABmAAAAAAAAAGcAAAAAAAAAAAAAAGcAAAAAAAAAaAAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAYQAAAAAAAABhAAAAAAAAAP///////+9/" - ], - [ - 10, - 1, - "insert", - { - "characters": " tests/" - }, - "BwAAAIQAAAAAAAAAhQAAAAAAAAAAAAAAhQAAAAAAAACGAAAAAAAAAAAAAACGAAAAAAAAAIcAAAAAAAAAAAAAAIcAAAAAAAAAiAAAAAAAAAAAAAAAiAAAAAAAAACJAAAAAAAAAAAAAACJAAAAAAAAAIoAAAAAAAAAAAAAAIoAAAAAAAAAiwAAAAAAAAAAAAAA", - "AQAAAAAAAAABAAAAhAAAAAAAAACEAAAAAAAAAAAAAAAAIINA" - ] - ] - }, - { - "file": "stacosys/db/dao.py", - "settings": - { - "buffer_size": 2026, - "line_ending": "Unix" - }, - "undo_stack": - [ - ] + } } ], "build_system": "", @@ -6145,6 +99,14 @@ "last_filter": "", "selected_items": [ + [ + "dia", + "LSP: Toggle Diagnostics Panel" + ], + [ + "black", + "python-black: Create Black Configuration File" + ], [ "insta", "Package Control: Install Package" @@ -6287,23 +249,25 @@ ], "file_history": [ - "/home/yannic/work/stacosys/stacosys/db/dao.py", + "/home/yannic/work/stacosys/stacosys/db/__init__.py", + "/home/yannic/work/stacosys/tests/test_api.py", "/home/yannic/work/stacosys/tests/test_db.py", + "/home/yannic/work/stacosys/stacosys/service/configuration.py", + "/home/yannic/work/stacosys/Makefile", + "/home/yannic/work/stacosys/stacosys/db/dao.py", + "/home/yannic/work/stacosys/pyproject.toml", "/home/yannic/work/stacosys/stacosys/run.py", "/home/yannic/work/stacosys/config-dev.ini", "/home/yannic/work/stacosys/dbmigration/migrate_from_3.3_to_4.0.py", "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", "/home/yannic/work/stacosys/stacosys/model/comment.py", - "/home/yannic/work/stacosys/tests/test_api.py", "/home/yannic/work/stacosys/tests/test_form.py", "/home/yannic/work/stacosys/config.ini", - "/home/yannic/work/stacosys/stacosys/service/configuration.py", "/home/yannic/work/stacosys/tests/test_config.py", "/home/yannic/work/stacosys/dbmigration/migrate_from_1.1_to_2.0.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/pydal/objects.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/markdown/extensions/def_list.py", "/home/yannic/work/stacosys/stacosys/db/database.py", - "/home/yannic/work/stacosys/pyproject.toml", "/home/yannic/work/stacosys/tests/test_rssfeed.py", "/home/yannic/work/stacosys/stacosys/service/mail.py", "/home/yannic/work/stacosys/stacosys/interface/form.py", @@ -6312,7 +276,6 @@ "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/peewee.py", "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", "/home/yannic/work/stacosys/tests/test_mail.py", - "/home/yannic/work/stacosys/Makefile", "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", "/home/yannic/work/stacosys/.venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi", "/usr/lib64/python3.11/smtplib.py", @@ -6339,7 +302,7 @@ ], "find": { - "height": 40.0 + "height": 28.0 }, "find_in_files": { @@ -6354,6 +317,7 @@ "case_sensitive": false, "find_history": [ + "bla", "find_not_published_comments", "find_comment_by_id", "asdict", @@ -6390,237 +354,20 @@ [ { "buffer": 0, - "file": "stacosys/db/__init__.py", - "semi_transient": false, - "settings": - { - "buffer_size": 701, - "regions": - { - }, - "selection": - [ - [ - 148, - 148 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 5, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/__init__.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 5, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 1, - "file": "tests/test_api.py", - "semi_transient": false, - "settings": - { - "buffer_size": 1625, - "regions": - { - }, - "selection": - [ - [ - 699, - 699 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 2, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_api.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 4, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 2, - "file": "tests/test_db.py", - "semi_transient": false, - "settings": - { - "buffer_size": 2740, - "regions": - { - }, - "selection": - [ - [ - 2075, - 2075 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 2, - "lsp_uri": "file:///home/yannic/work/stacosys/tests/test_db.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 399.0, - "zoom_level": 1.0 - }, - "stack_index": 1, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 3, + "file": "stacosys/service/rssfeed.py", "selected": true, - "semi_transient": false, - "settings": - { - "buffer_size": 506, - "regions": - { - }, - "selection": - [ - [ - 506, - 506 - ] - ], - "settings": - { - "auto_indent": false, - "lsp_uri": "buffer://sublime/109", - "syntax": "Packages/Text/Plain text.tmLanguage", - "tab_width": 2, - "word_wrap": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 0, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 4, - "file": "Makefile", - "semi_transient": false, - "settings": - { - "buffer_size": 338, - "regions": - { - }, - "selection": - [ - [ - 106, - 139 - ] - ], - "settings": - { - "lsp_uri": "file:///home/yannic/work/stacosys/Makefile", - "syntax": "Packages/Makefile/Makefile.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": false - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 3, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 5, - "file": "stacosys/db/dao.py", "semi_transient": true, "settings": { - "buffer_size": 2026, + "buffer_size": 1754, "regions": { }, "selection": [ [ - 724, - 751 + 0, + 0 ] ], "settings": @@ -6643,17 +390,17 @@ ], "lsp_active": true, "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/db/dao.py", + "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/rssfeed.py", "show_definitions": false, "syntax": "Packages/Python/Python.sublime-syntax", "tab_size": 4, "translate_tabs_to_spaces": true }, "translation.x": 0.0, - "translation.y": 57.0, + "translation.y": 0.0, "zoom_level": 1.0 }, - "stack_index": 2, + "stack_index": 0, "stack_multiselect": false, "type": "text" } @@ -6757,6 +504,10 @@ "last_filter": "", "selected_items": [ + [ + "", + "~/work/blog/blog.sublime-project" + ] ], "width": 380.0 }, diff --git a/stacosys/service/rssfeed.py b/stacosys/service/rssfeed.py index 80461a1..dd3c3d5 100644 --- a/stacosys/service/rssfeed.py +++ b/stacosys/service/rssfeed.py @@ -52,7 +52,5 @@ class Rss: lastBuildDate=datetime.now(), items=items, ) - # TODO technical debt: replace pyRss2Gen - # TODO validate feed (https://validator.w3.org/feed/check.cgi) # pylint: disable=consider-using-with rss.write_xml(open(self._rss_file, "w", encoding="utf-8"), encoding="utf-8") diff --git a/tests/test_db.py b/tests/test_db.py index ebacd80..a47e8c6 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -2,6 +2,7 @@ # -*- coding: UTF-8 -*- import time + import pytest from stacosys.db import dao, database From 260e9de547431fe6e1a4db7bf8c20c6086defeb1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:09:08 +0100 Subject: [PATCH 496/586] README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a1875fa..18ba551 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) - [![Python version](https://img.shields.io/badge/Python-3.10-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Peewee version](https://img.shields.io/badge/Peewee-3.14-green.svg)](https://docs.peewee-orm.com/) + [![Python version](https://img.shields.io/badge/Python-3.11-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) @@ -41,7 +41,6 @@ Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-ba - [Python](https://www.python.org) - [Flask](http://flask.pocoo.org) -- [Peewee ORM](http://docs.peewee-orm.com) - [Markdown](http://daringfireball.net/projects/markdown) ### Installation From d153be8cf8a7c8bc38f306047f7cc09f1a693f54 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:15:22 +0100 Subject: [PATCH 497/586] License badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18ba551..de80ccf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![GitLicense](https://gitlicense.com/badge/kianby/stacosys)](https://gitlicense.com/license/kianby/stacosys) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.11-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) [![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) From 54f6b40d0a440f4cd12ed98759e48edde32a3bad Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:24:01 +0100 Subject: [PATCH 498/586] Fix containter launch --- docker/docker-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-init.sh b/docker/docker-init.sh index b573f35..f0042b8 100644 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -1,4 +1,4 @@ #!/bin/bash cd /stacosys -python3 run.py /config/config.ini +python3 stacosys/run.py /config/config.ini From c13d1fbec5680ef3b1b38c138fa3c61de6a41bef Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:31:13 +0100 Subject: [PATCH 499/586] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de80ccf..5bc5b84 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ Build and Dependency management relies on [Poetry](https://python-poetry.org/), ### Improvements -Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. +Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. \ No newline at end of file From 4176cc38c6eb60928a45a10cba17a5533dc2a780 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:47:02 +0100 Subject: [PATCH 500/586] Debug container --- Dockerfile | 1 - docker/docker-init.sh | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f985ffc..7ea52cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,6 @@ RUN chmod +x usr/local/bin/docker-init.sh RUN cd / COPY dist/${STACOSYS_FILENAME} / -#RUN wget https://github.com/kianby/stacosys/releases/download/${STACOSYS_VERSION}/${STACOSYS_FILENAME} RUN python3 -m pip install ${STACOSYS_FILENAME} --target /stacosys RUN rm -f ${STACOSYS_FILENAME} diff --git a/docker/docker-init.sh b/docker/docker-init.sh index f0042b8..7110ef4 100644 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -2,3 +2,5 @@ cd /stacosys python3 stacosys/run.py /config/config.ini + +tail -f /dev/null \ No newline at end of file From 2178e56df9804efbfd43149f866db0e65cb9e07f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 3 Dec 2022 19:54:15 +0100 Subject: [PATCH 501/586] workaround for container startup --- docker/docker-init.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/docker-init.sh b/docker/docker-init.sh index 7110ef4..7c51f8e 100644 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -1,6 +1,9 @@ #!/bin/bash cd /stacosys -python3 stacosys/run.py /config/config.ini +# workaround for startup +cp -f stacosys/run.py . +python3 run.py /config/config.ini -tail -f /dev/null \ No newline at end of file +# catch for debug +#tail -f /dev/null \ No newline at end of file From ded5adb3e7a7ab436431f6542c122ce703abcb9c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:46:25 +0100 Subject: [PATCH 502/586] set python version with pyenv. Create virtual env inside project --- poetry.lock | 311 ++++++++++++++++++++++++++++++------------------- poetry.toml | 3 + pyproject.toml | 2 +- 3 files changed, 197 insertions(+), 119 deletions(-) create mode 100644 poetry.toml diff --git a/poetry.lock b/poetry.lock index fc0d83d..b44caff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "astroid" -version = "2.12.13" +version = "2.13.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -8,21 +8,23 @@ python-versions = ">=3.7.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" +typing-extensions = ">=4.0.0" wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} [[package]] name = "attrs" -version = "22.1.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] [[package]] name = "background" @@ -34,7 +36,7 @@ python-versions = "*" [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -54,7 +56,7 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -62,14 +64,11 @@ python-versions = ">=3.6" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.0.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] +python-versions = "*" [[package]] name = "click" @@ -164,19 +163,19 @@ python-versions = ">=3.5" [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "isort" -version = "5.10.1" +version = "5.11.4" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.7.0" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] @@ -208,7 +207,7 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "lazy-object-proxy" -version = "1.8.0" +version = "1.9.0" description = "A fast and thorough lazy object proxy." category = "dev" optional = false @@ -269,18 +268,15 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.3" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +python-versions = ">=3.7" [[package]] name = "pathspec" -version = "0.10.2" +version = "0.10.3" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -288,15 +284,15 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.5.4" +version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -320,7 +316,7 @@ python-versions = "*" [[package]] name = "pylint" -version = "2.15.7" +version = "2.15.10" description = "python code static checker" category = "dev" optional = false @@ -329,7 +325,7 @@ python-versions = ">=3.7.2" [package.dependencies] astroid = ">=2.12.13,<=2.14.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = ">=0.2" +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" @@ -339,17 +335,6 @@ tomlkit = ">=0.10.1" spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pyrss2gen" version = "1.1" @@ -360,7 +345,7 @@ python-versions = "*" [[package]] name = "pytest" -version = "7.2.0" +version = "7.2.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -393,7 +378,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false @@ -401,7 +386,7 @@ python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" @@ -435,7 +420,7 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -470,52 +455,129 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" -python-versions = "~3.11" -content-hash = "7ee7b17fa42c245160e4e376453b3d6cea354551dd40342ee57d59b6d71ea31d" +python-versions = "^3.11.0" +content-hash = "f48c2bb51f31361e8c97fad6b813744d524fa28e81a5047844d53043fcf12398" [metadata.files] astroid = [ - {file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"}, - {file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"}, + {file = "astroid-2.13.2-py3-none-any.whl", hash = "sha256:8f6a8d40c4ad161d6fc419545ae4b2f275ed86d1c989c97825772120842ee0d2"}, + {file = "astroid-2.13.2.tar.gz", hash = "sha256:3bc7834720e1a24ca797fd785d77efb14f7a28ee8e635ef040b6e2d80ccb3303"}, ] attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] background = [ {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, ] black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, ] certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, ] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, @@ -597,12 +659,12 @@ idna = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, + {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, + {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, ] itsdangerous = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, @@ -613,25 +675,42 @@ jinja2 = [ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"}, - {file = "lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"}, - {file = "lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"}, - {file = "lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"}, - {file = "lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"}, - {file = "lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"}, - {file = "lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"}, - {file = "lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"}, - {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"}, - {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"}, - {file = "lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"}, - {file = "lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"}, - {file = "lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"}, - {file = "lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"}, - {file = "lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"}, - {file = "lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"}, - {file = "lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"}, - {file = "lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"}, - {file = "lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"}, + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] markdown = [ {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, @@ -720,16 +799,16 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -739,27 +818,23 @@ pydal = [ {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, ] pylint = [ - {file = "pylint-2.15.7-py3-none-any.whl", hash = "sha256:1d561d1d3e8be9dd880edc685162fbdaa0409c88b9b7400873c0cf345602e326"}, - {file = "pylint-2.15.7.tar.gz", hash = "sha256:91e4776dbcb4b4d921a3e4b6fec669551107ba11f29d9199154a01622e460a57"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, + {file = "pylint-2.15.10-py3-none-any.whl", hash = "sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e"}, + {file = "pylint-2.15.10.tar.gz", hash = "sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5"}, ] pyrss2gen = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] pytest = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, + {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, + {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, ] pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] tomlkit = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, @@ -774,8 +849,8 @@ typing-extensions = [ {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..be97f1e --- /dev/null +++ b/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +in-project = true +prefer-active-python = true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f8e7c83..88b9676 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Yax"] readme = "README.md" [tool.poetry.dependencies] -python = "~3.11" +python = "^3.11.0" pyrss2gen = "^1.1" markdown = "^3.1.1" requests = "^2.25.1" From dd7ca08b5a7bf5913595cec96752384577094fe7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 25 Mar 2023 16:50:58 +0100 Subject: [PATCH 503/586] Up dependencies --- poetry.lock | 1317 ++++++++++++++++++++++++++------------------------- 1 file changed, 662 insertions(+), 655 deletions(-) diff --git a/poetry.lock b/poetry.lock index b44caff..fab7ae0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,19 @@ +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. + [[package]] name = "astroid" -version = "2.13.2" +version = "2.15.0" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.0-py3-none-any.whl", hash = "sha256:e3e4d0ffc2d15d954065579689c36aac57a339a4679a679579af6401db4d3fdb"}, + {file = "astroid-2.15.0.tar.gz", hash = "sha256:525f126d5dc1b8b0b6ee398b33159105615d92dc4a17f2cd064125d57f6186fa"}, +] [package.dependencies] lazy-object-proxy = ">=1.4.0" -typing-extensions = ">=4.0.0" wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} [[package]] @@ -18,6 +23,10 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] [package.extras] cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] @@ -33,6 +42,10 @@ description = "It does what it says it does." category = "main" optional = false python-versions = "*" +files = [ + {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, + {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, +] [[package]] name = "black" @@ -41,6 +54,20 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -61,14 +88,95 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "charset-normalizer" -version = "3.0.1" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] [[package]] name = "click" @@ -77,6 +185,10 @@ description = "Composable command line interface toolkit" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -88,6 +200,10 @@ description = "Cross-platform colored terminal text." category = "main" 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" @@ -96,498 +212,7 @@ description = "Code coverage measurement for Python" category = "main" optional = false python-versions = ">=3.7" - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "coveralls" -version = "3.3.1" -description = "Show coverage stats online via coveralls.io" -category = "dev" -optional = false -python-versions = ">= 3.5" - -[package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" -docopt = ">=0.6.1" -requests = ">=1.0.0" - -[package.extras] -yaml = ["PyYAML (>=3.10)"] - -[[package]] -name = "dill" -version = "0.3.6" -description = "serialize all of python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "flask" -version = "2.2.2" -description = "A simple framework for building complex web applications." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=8.0" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.2.2" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "isort" -version = "5.11.4" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.7.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.9.0" -description = "A fast and thorough lazy object proxy." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "markdown" -version = "3.4.1" -description = "Python implementation of Markdown." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "mypy" -version = "0.991" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -mypy-extensions = ">=0.4.3" -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "pathspec" -version = "0.10.3" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.6.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pydal" -version = "20221110.1" -description = "a pure Python Database Abstraction Layer (for python version 2.7 and 3.x)" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pylint" -version = "2.15.10" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.7.2" - -[package.dependencies] -astroid = ">=2.12.13,<=2.14.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyrss2gen" -version = "1.1" -description = "Generate RSS2 using a Python data structure" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pytest" -version = "7.2.1" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "requests" -version = "2.28.2" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "tomlkit" -version = "0.11.6" -description = "Style preserving TOML library" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "types-markdown" -version = "3.4.2.1" -description = "Typing stubs for Markdown" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.14" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "werkzeug" -version = "2.2.2" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog"] - -[[package]] -name = "wrapt" -version = "1.14.1" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[metadata] -lock-version = "1.1" -python-versions = "^3.11.0" -content-hash = "f48c2bb51f31361e8c97fad6b813744d524fa28e81a5047844d53043fcf12398" - -[metadata.files] -astroid = [ - {file = "astroid-2.13.2-py3-none-any.whl", hash = "sha256:8f6a8d40c4ad161d6fc419545ae4b2f275ed86d1c989c97825772120842ee0d2"}, - {file = "astroid-2.13.2.tar.gz", hash = "sha256:3bc7834720e1a24ca797fd785d77efb14f7a28ee8e635ef040b6e2d80ccb3303"}, -] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -background = [ - {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, - {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, -] -black = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -charset-normalizer = [ - {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, - {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ +files = [ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, @@ -639,42 +264,158 @@ coverage = [ {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] -coveralls = [ + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "coveralls" +version = "3.3.1" +description = "Show coverage stats online via coveralls.io" +category = "dev" +optional = false +python-versions = ">= 3.5" +files = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] -dill = [ + +[package.dependencies] +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "dill" +version = "0.3.6" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, ] -docopt = [ + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] -flask = [ - {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, - {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, + +[[package]] +name = "flask" +version = "2.2.3" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"}, + {file = "Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d"}, ] -idna = [ + +[package.dependencies] +click = ">=8.0" +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.2.2" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -iniconfig = [ + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -isort = [ - {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, - {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, ] -itsdangerous = [ + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] -jinja2 = [ + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -lazy-object-proxy = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, @@ -712,57 +453,102 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] -markdown = [ - {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, - {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"}, + +[[package]] +name = "markdown" +version = "3.4.3" +description = "Python implementation of John Gruber's Markdown." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Markdown-3.4.3-py3-none-any.whl", hash = "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2"}, + {file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"}, ] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] -mccabe = [ + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -mypy = [ + +[[package]] +name = "mypy" +version = "0.991" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, @@ -794,131 +580,352 @@ mypy = [ {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + +[package.dependencies] +mypy-extensions = ">=0.4.3" +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -packaging = [ + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] -pathspec = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] -platformdirs = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + +[[package]] +name = "platformdirs" +version = "3.1.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, + {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, ] -pluggy = [ + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pydal = [ + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pydal" +version = "20221110.1" +description = "a pure Python Database Abstraction Layer (for python version 2.7 and 3.x)" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, ] -pylint = [ - {file = "pylint-2.15.10-py3-none-any.whl", hash = "sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e"}, - {file = "pylint-2.15.10.tar.gz", hash = "sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5"}, + +[[package]] +name = "pylint" +version = "2.17.1" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.1-py3-none-any.whl", hash = "sha256:8660a54e3f696243d644fca98f79013a959c03f979992c1ab59c24d3f4ec2700"}, + {file = "pylint-2.17.1.tar.gz", hash = "sha256:d4d009b0116e16845533bc2163493d6681846ac725eab8ca8014afb520178ddd"}, ] -pyrss2gen = [ + +[package.dependencies] +astroid = ">=2.15.0,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyrss2gen" +version = "1.1" +description = "Generate RSS2 using a Python data structure" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, ] -pytest = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, + +[[package]] +name = "pytest" +version = "7.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, ] -pytest-cov = [ + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] -requests = [ + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] -tomlkit = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, ] -types-markdown = [ - {file = "types-Markdown-3.4.2.1.tar.gz", hash = "sha256:03c0904cf5886a7d8193e2f50bcf842afc89e0ab80f060f389f6c2635c65628f"}, - {file = "types_Markdown-3.4.2.1-py3-none-any.whl", hash = "sha256:b2333f6f4b8f69af83de359e10a097e4a3f14bbd6d2484e1829d9b0ec56fa0cb"}, + +[[package]] +name = "types-markdown" +version = "3.4.2.5" +description = "Typing stubs for Markdown" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "types-Markdown-3.4.2.5.tar.gz", hash = "sha256:e76a2a07166f7c69aadf5fa51ef954d2baaed35eb8fa30c36f10877d8403fcef"}, + {file = "types_Markdown-3.4.2.5-py3-none-any.whl", hash = "sha256:6707853d2478f6ae9e298db497c93c38fe40d8f89e1d90872d992c3934aa76d7"}, ] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] -urllib3 = [ - {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + +[[package]] +name = "urllib3" +version = "1.26.15" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, ] -werkzeug = [ - {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, - {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "werkzeug" +version = "2.2.3" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, + {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, ] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11.0" +content-hash = "f48c2bb51f31361e8c97fad6b813744d524fa28e81a5047844d53043fcf12398" From a4f1b9ad4fda95b6b377c59caf8f7aea858d7691 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 25 Mar 2023 18:09:53 +0100 Subject: [PATCH 504/586] poetry run ./build.sh creates an executables with pyinstaller --- build.sh | 2 + poetry.lock | 118 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +- 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2e81e1e --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +pyinstaller stacosys/run.py --name stacosys --onefile \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index fab7ae0..f329d3d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,17 @@ # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +[[package]] +name = "altgraph" +version = "0.17.3" +description = "Python graph (network) package" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "altgraph-0.17.3-py2.py3-none-any.whl", hash = "sha256:c8ac1ca6772207179ed8003ce7687757c04b0b71536f81e2ac5755c6226458fe"}, + {file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"}, +] + [[package]] name = "astroid" version = "2.15.0" @@ -454,6 +466,21 @@ files = [ {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] +[[package]] +name = "macholib" +version = "1.16.2" +description = "Mach-O header analysis and editing" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "macholib-1.16.2-py2.py3-none-any.whl", hash = "sha256:44c40f2cd7d6726af8fa6fe22549178d3a4dfecc35a9cd15ea916d9c83a688e0"}, + {file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"}, +] + +[package.dependencies] +altgraph = ">=0.17" + [[package]] name = "markdown" version = "3.4.3" @@ -627,6 +654,18 @@ files = [ {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] +[[package]] +name = "pefile" +version = "2023.2.7" +description = "Python PE parsing module" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, + {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, +] + [[package]] name = "platformdirs" version = "3.1.1" @@ -670,6 +709,52 @@ files = [ {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, ] +[[package]] +name = "pyinstaller" +version = "5.9.0" +description = "PyInstaller bundles a Python application and all its dependencies into a single package." +category = "dev" +optional = false +python-versions = "<3.12,>=3.7" +files = [ + {file = "pyinstaller-5.9.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:93d7e8443a6b60745d42aa50f08730f6b419410832b4c616c4f1bb315f087661"}, + {file = "pyinstaller-5.9.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3b2c34c3c3ddf38f68d9f5afbed82abe0f89d53014c56892326fef10172ee652"}, + {file = "pyinstaller-5.9.0-py3-none-manylinux2014_i686.whl", hash = "sha256:dcd348b174fd72c4df271790ac582969c9423cb099fe92db9ec131a8a9243d5a"}, + {file = "pyinstaller-5.9.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4b21b0298db44f5f07fc04d8ff81ec31efa47b72798efaecc4e811c50a102111"}, + {file = "pyinstaller-5.9.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:12ca6567be457826e14416637ea54485a185d0ce7a5a044df0d0daf588fff6d1"}, + {file = "pyinstaller-5.9.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7dd156c2438f197c168b990bbce03c97d3fb758dd9bbc3ca93626c2f4473a47"}, + {file = "pyinstaller-5.9.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:2ba42038b3bd83e1fba7c8eb9e7cde43bd5938e37ca542c89e8779355d213f52"}, + {file = "pyinstaller-5.9.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ff94347183ae3755cfb8f02e64744eb7fe384469bd61e453c6ff59a81665d6"}, + {file = "pyinstaller-5.9.0-py3-none-win32.whl", hash = "sha256:8476538aec8a0a3be4f74b93388bd6989b91cc437ff86d6f0d3a68961176dce6"}, + {file = "pyinstaller-5.9.0-py3-none-win_amd64.whl", hash = "sha256:e7a4c292810285c2466f3bdcb1e03ba2170177ebe3d7054ff1af3bb348bf61a4"}, + {file = "pyinstaller-5.9.0-py3-none-win_arm64.whl", hash = "sha256:6cf6c032c72ef78fd9aa5e47d8952e784db45b2c3f7862bd44a99df68c216f64"}, + {file = "pyinstaller-5.9.0.tar.gz", hash = "sha256:2bde16a8d664e8eba9aa7b84f729f7ab005c1793be4fe1986b3c9cad6c486622"}, +] + +[package.dependencies] +altgraph = "*" +macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} +pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2021.4" +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +setuptools = ">=42.0.0" + +[package.extras] +encryption = ["tinyaes (>=1.0.0)"] +hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2023.1" +description = "Community maintained hooks for PyInstaller" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyinstaller-hooks-contrib-2023.1.tar.gz", hash = "sha256:ab56c192e7cd4472ff6b840cda4fc42bceccc7fb4234f064fc834a3248c0afdd"}, + {file = "pyinstaller_hooks_contrib-2023.1-py2.py3-none-any.whl", hash = "sha256:d2ea40a7105651aa525bfe5fe309aa264d4d9bb49f839b862243dcf0a56c34cd"}, +] + [[package]] name = "pylint" version = "2.17.1" @@ -747,6 +832,18 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] + [[package]] name = "requests" version = "2.28.2" @@ -769,6 +866,23 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "67.6.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.6.0-py3-none-any.whl", hash = "sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2"}, + {file = "setuptools-67.6.0.tar.gz", hash = "sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "tomlkit" version = "0.11.6" @@ -927,5 +1041,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.11.0" -content-hash = "f48c2bb51f31361e8c97fad6b813744d524fa28e81a5047844d53043fcf12398" +python-versions = ">=3.11.0,<3.12" +content-hash = "4d50d8e3df593e5b2460ffa6b34a5bb9f24e175edf5566d44f1e875733554075" diff --git a/pyproject.toml b/pyproject.toml index 88b9676..c2da9c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Yax"] readme = "README.md" [tool.poetry.dependencies] -python = "^3.11.0" +python = ">=3.11.0,<3.12" pyrss2gen = "^1.1" markdown = "^3.1.1" requests = "^2.25.1" @@ -23,6 +23,7 @@ pytest = "^7.2.0" coveralls = "^3.3.1" pytest-cov = "^4.0.0" black = "^22.10.0" +pyinstaller = "^5.9.0" [build-system] requires = ["poetry-core>=1.0.0"] From 6aa0686aa430acd548dc5d876a7c0f3c562b8bd7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 26 Mar 2023 20:04:48 +0200 Subject: [PATCH 505/586] pyinstall spec file handling ninja templates --- .gitignore | 2 +- stacosys.spec | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 stacosys.spec diff --git a/.gitignore b/.gitignore index 3388d87..1642124 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ var/ # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec +#*.spec # Installer logs pip-log.txt diff --git a/stacosys.spec b/stacosys.spec new file mode 100644 index 0000000..85a4e01 --- /dev/null +++ b/stacosys.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis( + ['stacosys/run.py'], + pathex=[], + binaries=[], + datas=[('stacosys/interface/templates/*.html', 'stacosys/interface/templates/')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='stacosys', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) From 875d8ba15acb8b08a1043f3382dd882d2bd951c0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 28 Mar 2023 19:41:44 +0200 Subject: [PATCH 506/586] README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5bc5b84..67237e4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-ba Build and Dependency management relies on [Poetry](https://python-poetry.org/), but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/r/kianby/stacosys). +Build executable with pyinstaller + + poetry run pyinstaller stacosys.spec + ### Improvements Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. \ No newline at end of file From 9c839e793d7a6ddc74c78da632822b7d9f249ed9 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 28 Mar 2023 19:46:04 +0200 Subject: [PATCH 507/586] python <3.11 allowed --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c2da9c6..796fb44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Yax"] readme = "README.md" [tool.poetry.dependencies] -python = ">=3.11.0,<3.12" +python = ">=3.9.0,<3.12" pyrss2gen = "^1.1" markdown = "^3.1.1" requests = "^2.25.1" From 57ea3b1f3c0b402a13859d86fc770e42267dccff Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Apr 2023 17:54:55 +0200 Subject: [PATCH 508/586] Remove IDE config --- stacosys.sublime-project | 19 -- stacosys.sublime-workspace | 536 ------------------------------------- 2 files changed, 555 deletions(-) delete mode 100644 stacosys.sublime-project delete mode 100644 stacosys.sublime-workspace diff --git a/stacosys.sublime-project b/stacosys.sublime-project deleted file mode 100644 index 7d00994..0000000 --- a/stacosys.sublime-project +++ /dev/null @@ -1,19 +0,0 @@ -{ - "folders": - [ - { - "path": ".", - "folder_exclude_patterns": ["__pycache__",".pytest_cache", ".mypy_cache"], - "file_exclude_patterns": [".coverage"] - } - ], - "settings": { - "LSP": { - "LSP-pyright": { - "settings": { - "python.analysis.diagnosticMode": "workspace" - } - } - } - } -} diff --git a/stacosys.sublime-workspace b/stacosys.sublime-workspace deleted file mode 100644 index d1be777..0000000 --- a/stacosys.sublime-workspace +++ /dev/null @@ -1,536 +0,0 @@ -{ - "auto_complete": - { - "selected_items": - [ - [ - "init", - "init_config" - ], - [ - "author", - "author_name=" - ], - [ - "autho", - "author_name" - ], - [ - "au", - "author_gravatar=" - ], - [ - "auth", - "author_site=" - ], - [ - "db", - "db_uri" - ], - [ - "db_", - "db_uri" - ], - [ - "r", - "rss" - ], - [ - "c", - "check" - ], - [ - "con", - "configure_destination" - ], - [ - "EXP", - "EXPECTED_HTTP_PORT" - ], - [ - "l", - "login" - ], - [ - "assertI", - "assertIsNot" - ], - [ - "Confi", - "ConfigParameter" - ], - [ - "S", - "SITE_URL" - ], - [ - "Config", - "ConfigParameter" - ], - [ - "s", - "SITE_REDIRECT" - ], - [ - "get", - "get_int" - ] - ] - }, - "buffers": - [ - { - "file": "stacosys/service/rssfeed.py", - "settings": - { - "buffer_size": 1754, - "line_ending": "Unix" - } - } - ], - "build_system": "", - "build_system_choices": - [ - ], - "build_varint": "", - "command_palette": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - [ - "dia", - "LSP: Toggle Diagnostics Panel" - ], - [ - "black", - "python-black: Create Black Configuration File" - ], - [ - "insta", - "Package Control: Install Package" - ], - [ - "break", - "Python Breakpoints: Toggle" - ], - [ - "togg", - "Python Breakpoints: Toggle" - ], - [ - "diag", - "LSP: Toggle Diagnostics Panel" - ], - [ - "lsp", - "LSP: Toggle Diagnostics Panel" - ], - [ - "comment", - "Toggle Comment" - ], - [ - "install", - "Package Control: Install Package" - ], - [ - "rename", - "LSP: Rename" - ], - [ - "brea", - "Python Breakpoints: Clear All" - ], - [ - "comm", - "Toggle Comment" - ], - [ - "move", - "File: Move…" - ], - [ - "remove", - "Package Control: Remove Package" - ], - [ - "make", - "Build With: Make" - ], - [ - "python", - "Python Breakpoints: Toggle" - ], - [ - "docstr", - "AutoDocstring: Current" - ], - [ - "Package Control: ", - "Package Control: Install Package" - ], - [ - "distr", - "View: Toggle Distraction Free" - ], - [ - "upg", - "Package Control: Upgrade Package" - ], - [ - "form", - "LSP: Format Document" - ], - [ - "format", - "LSP: Format Document" - ], - [ - "lspc", - "LSP: Clear Diagnostics" - ], - [ - "ren", - "LSP: Rename Symbol" - ], - [ - "instal", - "Package Control: Install Package" - ], - [ - "package re", - "Package Control: Remove Package" - ], - [ - "paka", - "Package Control: Remove Package" - ], - [ - "inst", - "Package Control: Install Package" - ], - [ - "pac", - "Package Control: Remove Package" - ] - ], - "width": 0.0 - }, - "console": - { - "height": 164.0, - "history": - [ - ] - }, - "distraction_free": - { - "menu_visible": true, - "show_minimap": false, - "show_open_files": false, - "show_tabs": false, - "side_bar_visible": false, - "status_bar_visible": false - }, - "expanded_folders": - [ - "/home/yannic/work/stacosys", - "/home/yannic/work/stacosys/dbmigration", - "/home/yannic/work/stacosys/docker", - "/home/yannic/work/stacosys/stacosys", - "/home/yannic/work/stacosys/stacosys/db", - "/home/yannic/work/stacosys/stacosys/interface", - "/home/yannic/work/stacosys/stacosys/interface/web", - "/home/yannic/work/stacosys/stacosys/model", - "/home/yannic/work/stacosys/stacosys/service", - "/home/yannic/work/stacosys/tests" - ], - "file_history": - [ - "/home/yannic/work/stacosys/stacosys/db/__init__.py", - "/home/yannic/work/stacosys/tests/test_api.py", - "/home/yannic/work/stacosys/tests/test_db.py", - "/home/yannic/work/stacosys/stacosys/service/configuration.py", - "/home/yannic/work/stacosys/Makefile", - "/home/yannic/work/stacosys/stacosys/db/dao.py", - "/home/yannic/work/stacosys/pyproject.toml", - "/home/yannic/work/stacosys/stacosys/run.py", - "/home/yannic/work/stacosys/config-dev.ini", - "/home/yannic/work/stacosys/dbmigration/migrate_from_3.3_to_4.0.py", - "/home/yannic/work/stacosys/stacosys/service/rssfeed.py", - "/home/yannic/work/stacosys/stacosys/model/comment.py", - "/home/yannic/work/stacosys/tests/test_form.py", - "/home/yannic/work/stacosys/config.ini", - "/home/yannic/work/stacosys/tests/test_config.py", - "/home/yannic/work/stacosys/dbmigration/migrate_from_1.1_to_2.0.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/pydal/objects.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/markdown/extensions/def_list.py", - "/home/yannic/work/stacosys/stacosys/db/database.py", - "/home/yannic/work/stacosys/tests/test_rssfeed.py", - "/home/yannic/work/stacosys/stacosys/service/mail.py", - "/home/yannic/work/stacosys/stacosys/interface/form.py", - "/home/yannic/work/stacosys/stacosys/interface/__init__.py", - "/home/yannic/work/stacosys/stacosys/interface/api.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/peewee.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/background.py", - "/home/yannic/work/stacosys/tests/test_mail.py", - "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/builtins.pyi", - "/home/yannic/work/stacosys/.venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi", - "/usr/lib64/python3.11/smtplib.py", - "/home/yannic/work/stacosys/stacosys/interface/web/admin.py", - "/home/yannic/work/stacosys/comments.xml", - "/home/yannic/work/stacosys/stacosys/service/__init__.py", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/PyRSS2Gen.py", - "/home/yannic/work/blog/README.md", - "/home/yannic/work/blog/Dockerfile", - "/usr/lib64/python3.11/logging/__init__.py", - "/home/yannic/work/stacosys/stacosys/core/__init__.py", - "/home/yannic/work/stacosys/stacosys/core/configuration.py", - "/home/yannic/work/stacosys/.pylintrc", - "/home/yannic/work/stacosys/stacosys/core/mailer.py", - "/home/yannic/work/stacosys/stacosys/conf/config.py", - "/home/yannic/work/stacosys/stacosys/core/rss.py", - "/home/yannic/work/stacosys/run.sh", - "/home/yannic/work/stacosys/stacosys.sublime-project", - "/home/yannic/work/stacosys/pylintrc", - "/home/yannic/work/stacosys/.venv/lib/python3.11/site-packages/flask/app.py", - "/home/yannic/.cache/sublime-text-3/Package Storage/LSP-pyright/18.7.0/language-server/node_modules/pyright/dist/typeshed-fallback/stdlib/sys.pyi", - "/home/yannic/work/stacosys/flake8.ini", - "/home/yannic/work/stacosys/stacosys/__init__.py" - ], - "find": - { - "height": 28.0 - }, - "find_in_files": - { - "height": 104.0, - "where_history": - [ - "" - ] - }, - "find_state": - { - "case_sensitive": false, - "find_history": - [ - "bla", - "find_not_published_comments", - "find_comment_by_id", - "asdict", - "def delete", - "db_dal", - "tox", - "apscheduler", - "_lang", - "config", - "SITE_TOKE", - "app.conf", - "disable", - "background" - ], - "highlight": true, - "in_selection": false, - "preserve_case": false, - "regex": false, - "replace_history": - [ - ], - "reverse": false, - "scrollbar_highlights": true, - "show_context": true, - "use_buffer2": true, - "use_gitignore": true, - "whole_word": false, - "wrap": true - }, - "groups": - [ - { - "sheets": - [ - { - "buffer": 0, - "file": "stacosys/service/rssfeed.py", - "selected": true, - "semi_transient": true, - "settings": - { - "buffer_size": 1754, - "regions": - { - }, - "selection": - [ - [ - 0, - 0 - ] - ], - "settings": - { - "auto_complete_triggers": - [ - { - "characters": "<", - "selector": "text.html, text.xml" - }, - { - "rhs_empty": true, - "selector": "punctuation.accessor" - }, - { - "characters": ".[", - "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", - "server": "LSP-pyright" - } - ], - "lsp_active": true, - "lsp_hover_provider_count": 1, - "lsp_uri": "file:///home/yannic/work/stacosys/stacosys/service/rssfeed.py", - "show_definitions": false, - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 0, - "stack_multiselect": false, - "type": "text" - } - ] - } - ], - "incremental_find": - { - "height": 28.0 - }, - "input": - { - "height": 40.0 - }, - "layout": - { - "cells": - [ - [ - 0, - 0, - 1, - 1 - ] - ], - "cols": - [ - 0.0, - 1.0 - ], - "rows": - [ - 0.0, - 1.0 - ] - }, - "menu_visible": true, - "output.LSP Log Panel": - { - "height": 0.0 - }, - "output.diagnostics": - { - "height": 261.0 - }, - "output.exec": - { - "height": 132.0 - }, - "output.find_results": - { - "height": 0.0 - }, - "output.language servers": - { - "height": 132.0 - }, - "output.mdpopups": - { - "height": 0.0 - }, - "output.references": - { - "height": 208.0 - }, - "pinned_build_system": "", - "project": "stacosys.sublime-project", - "replace": - { - "height": 52.0 - }, - "save_all_on_build": true, - "select_file": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - [ - "def del", - ".venv/lib/python3.11/site-packages/markdown/extensions/def_list.py" - ], - [ - "socket.p", - ".venv/lib64/python3.11/site-packages/mypy/typeshed/stdlib/socket.pyi" - ], - [ - "mail", - "stacosys/service/mail.py" - ], - [ - "conf", - "stacosys/service/configuration.py" - ] - ], - "width": 0.0 - }, - "select_project": - { - "height": 500.0, - "last_filter": "", - "selected_items": - [ - [ - "", - "~/work/blog/blog.sublime-project" - ] - ], - "width": 380.0 - }, - "select_symbol": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - ], - "width": 0.0 - }, - "selected_group": 0, - "settings": - { - }, - "show_minimap": true, - "show_open_files": false, - "show_tabs": true, - "side_bar_visible": true, - "side_bar_width": 299.0, - "status_bar_visible": true, - "template_settings": - { - } -} From 43b50f75d95883937a362a6834a6be33f3ee4210 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 15 Apr 2023 17:56:09 +0200 Subject: [PATCH 509/586] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1642124..81aa1d1 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,5 @@ config-server.ini config-dev.ini .idea/ .python-version +stacosys.sublime-project +stacosys.sublime-workspace From 7946b342b2c0b843673debca922d6fc8f655b116 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 18 Jul 2023 20:51:11 +0200 Subject: [PATCH 511/586] Upgrade deps --- poetry.lock | 590 +++++++++++++++++++++++++++++----------------------- 1 file changed, 334 insertions(+), 256 deletions(-) diff --git a/poetry.lock b/poetry.lock index f329d3d..a6585ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,39 +14,24 @@ files = [ [[package]] name = "astroid" -version = "2.15.0" +version = "2.15.6" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false python-versions = ">=3.7.2" files = [ - {file = "astroid-2.15.0-py3-none-any.whl", hash = "sha256:e3e4d0ffc2d15d954065579689c36aac57a339a4679a679579af6401db4d3fdb"}, - {file = "astroid-2.15.0.tar.gz", hash = "sha256:525f126d5dc1b8b0b6ee398b33159105615d92dc4a17f2cd064125d57f6186fa"}, + {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, ] [package.dependencies] lazy-object-proxy = ">=1.4.0" -wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, ] -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - [[package]] name = "background" version = "0.2.1" @@ -86,6 +71,8 @@ click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -93,113 +80,125 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.5" description = "Composable command line interface toolkit" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, + {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, ] [package.dependencies] @@ -277,6 +276,9 @@ files = [ {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -327,22 +329,39 @@ files = [ ] [[package]] -name = "flask" -version = "2.2.3" -description = "A simple framework for building complex web applications." -category = "main" +name = "exceptiongroup" +version = "1.1.2" +description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"}, - {file = "Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d"}, + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flask" +version = "2.3.2" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, + {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, ] [package.dependencies] -click = ">=8.0" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.2.2" +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.3" [package.extras] async = ["asgiref (>=3.2)"] @@ -360,6 +379,26 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -493,67 +532,70 @@ files = [ {file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"}, ] +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + [package.extras] testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] @@ -610,6 +652,7 @@ files = [ [package.dependencies] mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" [package.extras] @@ -632,14 +675,14 @@ files = [ [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] @@ -668,30 +711,30 @@ files = [ [[package]] name = "platformdirs" -version = "3.1.1" +version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, - {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.extras] @@ -711,24 +754,24 @@ files = [ [[package]] name = "pyinstaller" -version = "5.9.0" +version = "5.13.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." category = "dev" optional = false -python-versions = "<3.12,>=3.7" +python-versions = "<3.13,>=3.7" files = [ - {file = "pyinstaller-5.9.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:93d7e8443a6b60745d42aa50f08730f6b419410832b4c616c4f1bb315f087661"}, - {file = "pyinstaller-5.9.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3b2c34c3c3ddf38f68d9f5afbed82abe0f89d53014c56892326fef10172ee652"}, - {file = "pyinstaller-5.9.0-py3-none-manylinux2014_i686.whl", hash = "sha256:dcd348b174fd72c4df271790ac582969c9423cb099fe92db9ec131a8a9243d5a"}, - {file = "pyinstaller-5.9.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4b21b0298db44f5f07fc04d8ff81ec31efa47b72798efaecc4e811c50a102111"}, - {file = "pyinstaller-5.9.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:12ca6567be457826e14416637ea54485a185d0ce7a5a044df0d0daf588fff6d1"}, - {file = "pyinstaller-5.9.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7dd156c2438f197c168b990bbce03c97d3fb758dd9bbc3ca93626c2f4473a47"}, - {file = "pyinstaller-5.9.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:2ba42038b3bd83e1fba7c8eb9e7cde43bd5938e37ca542c89e8779355d213f52"}, - {file = "pyinstaller-5.9.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ff94347183ae3755cfb8f02e64744eb7fe384469bd61e453c6ff59a81665d6"}, - {file = "pyinstaller-5.9.0-py3-none-win32.whl", hash = "sha256:8476538aec8a0a3be4f74b93388bd6989b91cc437ff86d6f0d3a68961176dce6"}, - {file = "pyinstaller-5.9.0-py3-none-win_amd64.whl", hash = "sha256:e7a4c292810285c2466f3bdcb1e03ba2170177ebe3d7054ff1af3bb348bf61a4"}, - {file = "pyinstaller-5.9.0-py3-none-win_arm64.whl", hash = "sha256:6cf6c032c72ef78fd9aa5e47d8952e784db45b2c3f7862bd44a99df68c216f64"}, - {file = "pyinstaller-5.9.0.tar.gz", hash = "sha256:2bde16a8d664e8eba9aa7b84f729f7ab005c1793be4fe1986b3c9cad6c486622"}, + {file = "pyinstaller-5.13.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fdd319828de679f9c5e381eff998ee9b4164bf4457e7fca56946701cf002c3f"}, + {file = "pyinstaller-5.13.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0df43697c4914285ecd333be968d2cd042ab9b2670124879ee87931d2344eaf5"}, + {file = "pyinstaller-5.13.0-py3-none-manylinux2014_i686.whl", hash = "sha256:28d9742c37e9fb518444b12f8c8ab3cb4ba212d752693c34475c08009aa21ccf"}, + {file = "pyinstaller-5.13.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e5fb17de6c325d3b2b4ceaeb55130ad7100a79096490e4c5b890224406fa42f4"}, + {file = "pyinstaller-5.13.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:78975043edeb628e23a73fb3ef0a273cda50e765f1716f75212ea3e91b09dede"}, + {file = "pyinstaller-5.13.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:cd7d5c06f2847195a23d72ede17c60857d6f495d6f0727dc6c9bc1235f2eb79c"}, + {file = "pyinstaller-5.13.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:24009eba63cfdbcde6d2634e9c87f545eb67249ddf3b514e0cd3b2cdaa595828"}, + {file = "pyinstaller-5.13.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:1fde4381155f21d6354dc450dcaa338cd8a40aaacf6bd22b987b0f3e1f96f3ee"}, + {file = "pyinstaller-5.13.0-py3-none-win32.whl", hash = "sha256:2d03419904d1c25c8968b0ad21da0e0f33d8d65716e29481b5bd83f7f342b0c5"}, + {file = "pyinstaller-5.13.0-py3-none-win_amd64.whl", hash = "sha256:9fc27c5a853b14a90d39c252707673c7a0efec921cd817169aff3af0fca8c127"}, + {file = "pyinstaller-5.13.0-py3-none-win_arm64.whl", hash = "sha256:3a331951f9744bc2379ea5d65d36f3c828eaefe2785f15039592cdc08560b262"}, + {file = "pyinstaller-5.13.0.tar.gz", hash = "sha256:5e446df41255e815017d96318e39f65a3eb807e74a796c7e7ff7f13b6366a2e9"}, ] [package.dependencies] @@ -736,7 +779,7 @@ altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} pyinstaller-hooks-contrib = ">=2021.4" -pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" [package.extras] @@ -745,36 +788,41 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.1" +version = "2023.5" description = "Community maintained hooks for PyInstaller" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.1.tar.gz", hash = "sha256:ab56c192e7cd4472ff6b840cda4fc42bceccc7fb4234f064fc834a3248c0afdd"}, - {file = "pyinstaller_hooks_contrib-2023.1-py2.py3-none-any.whl", hash = "sha256:d2ea40a7105651aa525bfe5fe309aa264d4d9bb49f839b862243dcf0a56c34cd"}, + {file = "pyinstaller-hooks-contrib-2023.5.tar.gz", hash = "sha256:cca6cdc31e739954b5bbbf05ef3f71fe448e9cdacad3a2197243bcf99bea2c00"}, + {file = "pyinstaller_hooks_contrib-2023.5-py2.py3-none-any.whl", hash = "sha256:e60185332a6b56691f471d364e9e9405b03091ca27c96e0dbebdedb7624457fd"}, ] [[package]] name = "pylint" -version = "2.17.1" +version = "2.17.4" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.1-py3-none-any.whl", hash = "sha256:8660a54e3f696243d644fca98f79013a959c03f979992c1ab59c24d3f4ec2700"}, - {file = "pylint-2.17.1.tar.gz", hash = "sha256:d4d009b0116e16845533bc2163493d6681846ac725eab8ca8014afb520178ddd"}, + {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, + {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, ] [package.dependencies] -astroid = ">=2.15.0,<=2.17.0-dev0" +astroid = ">=2.15.4,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -793,36 +841,37 @@ files = [ [[package]] name = "pytest" -version = "7.2.2" +version = "7.4.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, - {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "4.0.0" +version = "4.1.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] @@ -834,33 +883,33 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pywin32-ctypes" -version = "0.2.0" -description = "" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, ] [[package]] name = "requests" -version = "2.28.2" +version = "2.31.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -868,91 +917,104 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "67.6.0" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.6.0-py3-none-any.whl", hash = "sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2"}, - {file = "setuptools-67.6.0.tar.gz", hash = "sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "tomlkit" -version = "0.11.6" +version = "0.11.8" description = "Style preserving TOML library" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, - {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] [[package]] name = "types-markdown" -version = "3.4.2.5" +version = "3.4.2.9" description = "Typing stubs for Markdown" category = "main" optional = false python-versions = "*" files = [ - {file = "types-Markdown-3.4.2.5.tar.gz", hash = "sha256:e76a2a07166f7c69aadf5fa51ef954d2baaed35eb8fa30c36f10877d8403fcef"}, - {file = "types_Markdown-3.4.2.5-py3-none-any.whl", hash = "sha256:6707853d2478f6ae9e298db497c93c38fe40d8f89e1d90872d992c3934aa76d7"}, + {file = "types-Markdown-3.4.2.9.tar.gz", hash = "sha256:0930057bea0a534e06bbc021d57520720ad2a65b363612614ab0599cc7f606a9"}, + {file = "types_Markdown-3.4.2.9-py3-none-any.whl", hash = "sha256:c23a8a4dd9313539a446ba3dc673a6a920d79580c406de10a5c85a16733890a7"}, ] [[package]] name = "typing-extensions" -version = "4.5.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" -version = "1.26.15" +version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "werkzeug" -version = "2.2.3" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false python-versions = ">=3.7" files = [ - {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, - {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, + {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, + {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] -watchdog = ["watchdog"] +watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" @@ -1039,7 +1101,23 @@ files = [ {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [metadata] lock-version = "2.0" -python-versions = ">=3.11.0,<3.12" -content-hash = "4d50d8e3df593e5b2460ffa6b34a5bb9f24e175edf5566d44f1e875733554075" +python-versions = ">=3.9.0,<3.12" +content-hash = "8bda48658818d8aa2efbc31e1475d1b35635edfbc4ef37ffeac9d77523d69d60" From b4c98a60b9315780700c2aa30335de8328adf09e Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:10:01 +0200 Subject: [PATCH 512/586] Update deps --- poetry.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/poetry.lock b/poetry.lock index a6585ae..fb34da8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,14 +94,14 @@ files = [ [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -191,14 +191,14 @@ files = [ [[package]] name = "click" -version = "8.1.5" +version = "8.1.6" description = "Composable command line interface toolkit" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, - {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] @@ -304,14 +304,14 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "dill" -version = "0.3.6" -description = "serialize all of python" +version = "0.3.7" +description = "serialize all of Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, - {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] [package.extras] @@ -788,14 +788,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.5" +version = "2023.6" description = "Community maintained hooks for PyInstaller" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.5.tar.gz", hash = "sha256:cca6cdc31e739954b5bbbf05ef3f71fe448e9cdacad3a2197243bcf99bea2c00"}, - {file = "pyinstaller_hooks_contrib-2023.5-py2.py3-none-any.whl", hash = "sha256:e60185332a6b56691f471d364e9e9405b03091ca27c96e0dbebdedb7624457fd"}, + {file = "pyinstaller-hooks-contrib-2023.6.tar.gz", hash = "sha256:596a72009d8692b043e0acbf5e1b476d93149900142ba01845dded91a0770cb5"}, + {file = "pyinstaller_hooks_contrib-2023.6-py2.py3-none-any.whl", hash = "sha256:aa6d7d038814df6aa7bec7bdbebc7cb4c693d3398df858f6062957f0797d397b"}, ] [[package]] @@ -958,14 +958,14 @@ files = [ [[package]] name = "types-markdown" -version = "3.4.2.9" +version = "3.4.2.10" description = "Typing stubs for Markdown" category = "main" optional = false python-versions = "*" files = [ - {file = "types-Markdown-3.4.2.9.tar.gz", hash = "sha256:0930057bea0a534e06bbc021d57520720ad2a65b363612614ab0599cc7f606a9"}, - {file = "types_Markdown-3.4.2.9-py3-none-any.whl", hash = "sha256:c23a8a4dd9313539a446ba3dc673a6a920d79580c406de10a5c85a16733890a7"}, + {file = "types-Markdown-3.4.2.10.tar.gz", hash = "sha256:11e3558d50e3bc1e3f52f3fe073788f4ab917a829374fb354476221c700629e8"}, + {file = "types_Markdown-3.4.2.10-py3-none-any.whl", hash = "sha256:543ff3027fda21c3149780bf835a721cd094c5729e9a87725f180569c960bff8"}, ] [[package]] @@ -982,14 +982,14 @@ files = [ [[package]] name = "urllib3" -version = "2.0.3" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] From d911d66e3f10c4a6ee4fe193ab7c12098d03cd38 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:14:51 +0200 Subject: [PATCH 513/586] Build py executable --- build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 2e81e1e..5da4ff8 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,3 @@ #!/bin/sh -pyinstaller stacosys/run.py --name stacosys --onefile \ No newline at end of file +#pyinstaller stacosys/run.py --name stacosys --onefile +poetry run pyinstaller stacosys.spec From e3961e95c287df7724edf715a11cdaa6e38e3ab2 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 19 Sep 2023 20:20:27 +0200 Subject: [PATCH 514/586] Update deps --- poetry.lock | 141 ++++++++++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/poetry.lock b/poetry.lock index fb34da8..1f80e7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -191,14 +191,14 @@ files = [ [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -330,14 +330,14 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -345,14 +345,14 @@ test = ["pytest (>=6)"] [[package]] name = "flask" -version = "2.3.2" +version = "2.3.3" description = "A simple framework for building complex web applications." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, - {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, + {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, + {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, ] [package.dependencies] @@ -361,7 +361,7 @@ click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" -Werkzeug = ">=2.3.3" +Werkzeug = ">=2.3.7" [package.extras] async = ["asgiref (>=3.2)"] @@ -522,20 +522,21 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.4.3" +version = "3.4.4" description = "Python implementation of John Gruber's Markdown." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Markdown-3.4.3-py3-none-any.whl", hash = "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2"}, - {file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"}, + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] testing = ["coverage", "pyyaml"] [[package]] @@ -687,14 +688,14 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -711,30 +712,30 @@ files = [ [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -754,24 +755,24 @@ files = [ [[package]] name = "pyinstaller" -version = "5.13.0" +version = "5.13.2" description = "PyInstaller bundles a Python application and all its dependencies into a single package." category = "dev" optional = false python-versions = "<3.13,>=3.7" files = [ - {file = "pyinstaller-5.13.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fdd319828de679f9c5e381eff998ee9b4164bf4457e7fca56946701cf002c3f"}, - {file = "pyinstaller-5.13.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0df43697c4914285ecd333be968d2cd042ab9b2670124879ee87931d2344eaf5"}, - {file = "pyinstaller-5.13.0-py3-none-manylinux2014_i686.whl", hash = "sha256:28d9742c37e9fb518444b12f8c8ab3cb4ba212d752693c34475c08009aa21ccf"}, - {file = "pyinstaller-5.13.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e5fb17de6c325d3b2b4ceaeb55130ad7100a79096490e4c5b890224406fa42f4"}, - {file = "pyinstaller-5.13.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:78975043edeb628e23a73fb3ef0a273cda50e765f1716f75212ea3e91b09dede"}, - {file = "pyinstaller-5.13.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:cd7d5c06f2847195a23d72ede17c60857d6f495d6f0727dc6c9bc1235f2eb79c"}, - {file = "pyinstaller-5.13.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:24009eba63cfdbcde6d2634e9c87f545eb67249ddf3b514e0cd3b2cdaa595828"}, - {file = "pyinstaller-5.13.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:1fde4381155f21d6354dc450dcaa338cd8a40aaacf6bd22b987b0f3e1f96f3ee"}, - {file = "pyinstaller-5.13.0-py3-none-win32.whl", hash = "sha256:2d03419904d1c25c8968b0ad21da0e0f33d8d65716e29481b5bd83f7f342b0c5"}, - {file = "pyinstaller-5.13.0-py3-none-win_amd64.whl", hash = "sha256:9fc27c5a853b14a90d39c252707673c7a0efec921cd817169aff3af0fca8c127"}, - {file = "pyinstaller-5.13.0-py3-none-win_arm64.whl", hash = "sha256:3a331951f9744bc2379ea5d65d36f3c828eaefe2785f15039592cdc08560b262"}, - {file = "pyinstaller-5.13.0.tar.gz", hash = "sha256:5e446df41255e815017d96318e39f65a3eb807e74a796c7e7ff7f13b6366a2e9"}, + {file = "pyinstaller-5.13.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:16cbd66b59a37f4ee59373a003608d15df180a0d9eb1a29ff3bfbfae64b23d0f"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8f6dd0e797ae7efdd79226f78f35eb6a4981db16c13325e962a83395c0ec7420"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_i686.whl", hash = "sha256:65133ed89467edb2862036b35d7c5ebd381670412e1e4361215e289c786dd4e6"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:7d51734423685ab2a4324ab2981d9781b203dcae42839161a9ee98bfeaabdade"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:2c2fe9c52cb4577a3ac39626b84cf16cf30c2792f785502661286184f162ae0d"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c63ef6133eefe36c4b2f4daf4cfea3d6412ece2ca218f77aaf967e52a95ac9b8"}, + {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:aadafb6f213549a5906829bb252e586e2cf72a7fbdb5731810695e6516f0ab30"}, + {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b2e1c7f5cceb5e9800927ddd51acf9cc78fbaa9e79e822c48b0ee52d9ce3c892"}, + {file = "pyinstaller-5.13.2-py3-none-win32.whl", hash = "sha256:421cd24f26144f19b66d3868b49ed673176765f92fa9f7914cd2158d25b6d17e"}, + {file = "pyinstaller-5.13.2-py3-none-win_amd64.whl", hash = "sha256:ddcc2b36052a70052479a9e5da1af067b4496f43686ca3cdda99f8367d0627e4"}, + {file = "pyinstaller-5.13.2-py3-none-win_arm64.whl", hash = "sha256:27cd64e7cc6b74c5b1066cbf47d75f940b71356166031deb9778a2579bb874c6"}, + {file = "pyinstaller-5.13.2.tar.gz", hash = "sha256:c8e5d3489c3a7cc5f8401c2d1f48a70e588f9967e391c3b06ddac1f685f8d5d2"}, ] [package.dependencies] @@ -788,30 +789,30 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.6" +version = "2023.8" description = "Community maintained hooks for PyInstaller" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.6.tar.gz", hash = "sha256:596a72009d8692b043e0acbf5e1b476d93149900142ba01845dded91a0770cb5"}, - {file = "pyinstaller_hooks_contrib-2023.6-py2.py3-none-any.whl", hash = "sha256:aa6d7d038814df6aa7bec7bdbebc7cb4c693d3398df858f6062957f0797d397b"}, + {file = "pyinstaller-hooks-contrib-2023.8.tar.gz", hash = "sha256:318ccc316fb2b8c0bbdff2456b444bf1ce0e94cb3948a0f4dd48f6fc33d41c01"}, + {file = "pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl", hash = "sha256:d091a52fbeed71cde0359aa9ad66288521a8441cfba163d9446606c5136c72a8"}, ] [[package]] name = "pylint" -version = "2.17.4" +version = "2.17.5" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, - {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, + {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, + {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, ] [package.dependencies] -astroid = ">=2.15.4,<=2.17.0-dev0" +astroid = ">=2.15.6,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -841,14 +842,14 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -917,20 +918,20 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "tomli" @@ -946,14 +947,14 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.8" +version = "0.12.1" description = "Style preserving TOML library" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, - {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] @@ -970,14 +971,14 @@ files = [ [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] @@ -1000,14 +1001,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "2.3.6" +version = "2.3.7" description = "The comprehensive WSGI web application library." category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, - {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, + {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"}, + {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"}, ] [package.dependencies] @@ -1103,18 +1104,18 @@ files = [ [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] From ddc05e0718f5561f790bb0d8e9f62c1e2e3ec3c6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:16:55 +0200 Subject: [PATCH 515/586] Poetry update --- poetry.lock | 288 ++++++++++++++++++++++++---------------------------- 1 file changed, 131 insertions(+), 157 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1f80e7d..2807a53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,27 +1,25 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "altgraph" -version = "0.17.3" +version = "0.17.4" description = "Python graph (network) package" -category = "dev" optional = false python-versions = "*" files = [ - {file = "altgraph-0.17.3-py2.py3-none-any.whl", hash = "sha256:c8ac1ca6772207179ed8003ce7687757c04b0b71536f81e2ac5755c6226458fe"}, - {file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"}, + {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, + {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, ] [[package]] name = "astroid" -version = "2.15.6" +version = "2.15.8" description = "An abstract syntax tree for Python with inference support." -category = "dev" optional = false python-versions = ">=3.7.2" files = [ - {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, - {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, ] [package.dependencies] @@ -36,7 +34,6 @@ wrapt = [ name = "background" version = "0.2.1" description = "It does what it says it does." -category = "main" optional = false python-versions = "*" files = [ @@ -48,7 +45,6 @@ files = [ name = "black" version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -84,7 +80,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "blinker" version = "1.6.2" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -96,7 +91,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -106,94 +100,107 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -208,7 +215,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -220,7 +226,6 @@ files = [ name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -286,7 +291,6 @@ toml = ["tomli"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -295,7 +299,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -306,7 +310,6 @@ yaml = ["PyYAML (>=3.10)"] name = "dill" version = "0.3.7" description = "serialize all of Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -321,7 +324,6 @@ graph = ["objgraph (>=1.7.2)"] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "dev" optional = false python-versions = "*" files = [ @@ -332,7 +334,6 @@ files = [ name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -347,7 +348,6 @@ test = ["pytest (>=6)"] name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -371,7 +371,6 @@ dotenv = ["python-dotenv"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -383,7 +382,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -403,7 +401,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -415,7 +412,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -433,7 +429,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -445,7 +440,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -463,7 +457,6 @@ i18n = ["Babel (>=2.7)"] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -507,14 +500,13 @@ files = [ [[package]] name = "macholib" -version = "1.16.2" +version = "1.16.3" description = "Mach-O header analysis and editing" -category = "dev" optional = false python-versions = "*" files = [ - {file = "macholib-1.16.2-py2.py3-none-any.whl", hash = "sha256:44c40f2cd7d6726af8fa6fe22549178d3a4dfecc35a9cd15ea916d9c83a688e0"}, - {file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"}, + {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, + {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, ] [package.dependencies] @@ -524,7 +516,6 @@ altgraph = ">=0.17" name = "markdown" version = "3.4.4" description = "Python implementation of John Gruber's Markdown." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -543,7 +534,6 @@ testing = ["coverage", "pyyaml"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -567,6 +557,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -603,7 +603,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -615,7 +614,6 @@ files = [ name = "mypy" version = "0.991" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -666,7 +664,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -676,21 +673,19 @@ files = [ [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -702,7 +697,6 @@ files = [ name = "pefile" version = "2023.2.7" description = "Python PE parsing module" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -712,14 +706,13 @@ files = [ [[package]] name = "platformdirs" -version = "3.10.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] @@ -730,7 +723,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -746,7 +738,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pydal" version = "20221110.1" description = "a pure Python Database Abstraction Layer (for python version 2.7 and 3.x)" -category = "main" optional = false python-versions = "*" files = [ @@ -757,7 +748,6 @@ files = [ name = "pyinstaller" version = "5.13.2" description = "PyInstaller bundles a Python application and all its dependencies into a single package." -category = "dev" optional = false python-versions = "<3.13,>=3.7" files = [ @@ -789,30 +779,28 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.8" +version = "2023.9" description = "Community maintained hooks for PyInstaller" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.8.tar.gz", hash = "sha256:318ccc316fb2b8c0bbdff2456b444bf1ce0e94cb3948a0f4dd48f6fc33d41c01"}, - {file = "pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl", hash = "sha256:d091a52fbeed71cde0359aa9ad66288521a8441cfba163d9446606c5136c72a8"}, + {file = "pyinstaller-hooks-contrib-2023.9.tar.gz", hash = "sha256:76084b5988e3957a9df169d2a935d65500136967e710ddebf57263f1a909cd80"}, + {file = "pyinstaller_hooks_contrib-2023.9-py2.py3-none-any.whl", hash = "sha256:f34f4c6807210025c8073ebe665f422a3aa2ac5f4c7ebf4c2a26cc77bebf63b5"}, ] [[package]] name = "pylint" -version = "2.17.5" +version = "2.17.7" description = "python code static checker" -category = "dev" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, - {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, ] [package.dependencies] -astroid = ">=2.15.6,<=2.17.0-dev0" +astroid = ">=2.15.8,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -833,7 +821,6 @@ testutils = ["gitpython (>3)"] name = "pyrss2gen" version = "1.1" description = "Generate RSS2 using a Python data structure" -category = "main" optional = false python-versions = "*" files = [ @@ -844,7 +831,6 @@ files = [ name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -867,7 +853,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -886,7 +871,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -898,7 +882,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -920,7 +903,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -937,7 +919,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -949,7 +930,6 @@ files = [ name = "tomlkit" version = "0.12.1" description = "Style preserving TOML library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -961,7 +941,6 @@ files = [ name = "types-markdown" version = "3.4.2.10" description = "Typing stubs for Markdown" -category = "main" optional = false python-versions = "*" files = [ @@ -973,7 +952,6 @@ files = [ name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -983,14 +961,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.6" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ] [package.extras] @@ -1001,14 +978,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "2.3.7" +version = "3.0.0" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"}, - {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"}, + {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, + {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, ] [package.dependencies] @@ -1021,7 +997,6 @@ watchdog = ["watchdog (>=2.3)"] name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1106,7 +1081,6 @@ files = [ name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From 31ceded4b45d35a0ffed3bfc3a87beff79cd8871 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:52:42 +0200 Subject: [PATCH 516/586] Poetry update --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2807a53..1516292 100644 --- a/poetry.lock +++ b/poetry.lock @@ -78,13 +78,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blinker" -version = "1.6.2" +version = "1.6.3" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.7" files = [ - {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, - {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, + {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, + {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, ] [[package]] @@ -514,20 +514,20 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.4.4" +version = "3.5" description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, - {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, + {file = "Markdown-3.5-py3-none-any.whl", hash = "sha256:4afb124395ce5fc34e6d9886dab977fd9ae987fc6e85689f08278cf0c69d4bf3"}, + {file = "Markdown-3.5.tar.gz", hash = "sha256:a807eb2e4778d9156c8f07876c6e4d50b5494c5665c4834f67b06459dfd877b3"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] @@ -779,13 +779,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.9" +version = "2023.10" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.9.tar.gz", hash = "sha256:76084b5988e3957a9df169d2a935d65500136967e710ddebf57263f1a909cd80"}, - {file = "pyinstaller_hooks_contrib-2023.9-py2.py3-none-any.whl", hash = "sha256:f34f4c6807210025c8073ebe665f422a3aa2ac5f4c7ebf4c2a26cc77bebf63b5"}, + {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"}, + {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"}, ] [[package]] @@ -939,13 +939,13 @@ files = [ [[package]] name = "types-markdown" -version = "3.4.2.10" +version = "3.5.0.0" description = "Typing stubs for Markdown" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "types-Markdown-3.4.2.10.tar.gz", hash = "sha256:11e3558d50e3bc1e3f52f3fe073788f4ab917a829374fb354476221c700629e8"}, - {file = "types_Markdown-3.4.2.10-py3-none-any.whl", hash = "sha256:543ff3027fda21c3149780bf835a721cd094c5729e9a87725f180569c960bff8"}, + {file = "types-Markdown-3.5.0.0.tar.gz", hash = "sha256:8c2f5526bba29feee24040d4694ced554d09ae46d1f1523b2b739558beabae42"}, + {file = "types_Markdown-3.5.0.0-py3-none-any.whl", hash = "sha256:f1ff987576f347ec83aca0c2ced45e64e1deb771e0a6189744480013ccb59c96"}, ] [[package]] @@ -961,13 +961,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] From 6e46eea8140dae4cdc8a8f890090c124bbf49a0b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:53:07 +0100 Subject: [PATCH 517/586] Code Quality --- stacosys/db/dao.py | 12 ++++++------ stacosys/service/configuration.py | 9 +++++---- stacosys/service/mail.py | 10 +++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 7d9abdb..5a91210 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -28,29 +28,29 @@ def delete_comment(comment: Comment): def find_not_notified_comments(): - return db()(db().comment.notified == None).select() + return db()(db().comment.notified is None).select() def find_not_published_comments(): - return db()(db().comment.published == None).select() + return db()(db().comment.published is None).select() def find_published_comments_by_url(url): - return db()((db().comment.url == url) & (db().comment.published != None)).select( + return db()((db().comment.url == url) & (db().comment.published is not None)).select( orderby=db().comment.published ) def count_published_comments(url): return ( - db()((db().comment.url == url) & (db().comment.published != None)).count() + db()((db().comment.url == url) & (db().comment.published is not None)).count() if url - else db()(db().comment.published != None).count() + else db()(db().comment.published is not None).count() ) def find_recent_published_comments(): - return db()(db().comment.published != None).select( + return db()(db().comment.published is not None).select( orderby=~db().comment.published, limitby=(0, 10) ) diff --git a/stacosys/service/configuration.py b/stacosys/service/configuration.py index 986409e..67129a4 100644 --- a/stacosys/service/configuration.py +++ b/stacosys/service/configuration.py @@ -36,12 +36,13 @@ class Config: def load(self, config_pathname): self._cfg.read(config_pathname) - def _split_key(self, key: ConfigParameter): + @staticmethod + def _split_key(key: ConfigParameter): section, param = str(key.value).split(".") if not param: param = section section = "" - return (section, param) + return section, param def exists(self, key: ConfigParameter): section, param = self._split_key(key) @@ -78,8 +79,8 @@ class Config: def check(self): for key in ConfigParameter: if not self.get(key): - return (False, key.value) - return (True, None) + return False, key.value + return True, None def __repr__(self): dict_repr = {} diff --git a/stacosys/service/mail.py b/stacosys/service/mail.py index d7fe5ca..8589e60 100644 --- a/stacosys/service/mail.py +++ b/stacosys/service/mail.py @@ -18,11 +18,11 @@ class Mailer: self._site_admin_email: str = "" def configure_smtp( - self, - smtp_host, - smtp_port, - smtp_login, - smtp_password, + self, + smtp_host, + smtp_port, + smtp_login, + smtp_password, ) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port From cfe2da56c02a00b00df3df26a8d87b2603d3a0ca Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:59:45 +0100 Subject: [PATCH 518/586] Revert "Code Quality" This reverts commit 6e46eea8140dae4cdc8a8f890090c124bbf49a0b. --- stacosys/db/dao.py | 12 ++++++------ stacosys/service/configuration.py | 9 ++++----- stacosys/service/mail.py | 10 +++++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/stacosys/db/dao.py b/stacosys/db/dao.py index 5a91210..7d9abdb 100644 --- a/stacosys/db/dao.py +++ b/stacosys/db/dao.py @@ -28,29 +28,29 @@ def delete_comment(comment: Comment): def find_not_notified_comments(): - return db()(db().comment.notified is None).select() + return db()(db().comment.notified == None).select() def find_not_published_comments(): - return db()(db().comment.published is None).select() + return db()(db().comment.published == None).select() def find_published_comments_by_url(url): - return db()((db().comment.url == url) & (db().comment.published is not None)).select( + return db()((db().comment.url == url) & (db().comment.published != None)).select( orderby=db().comment.published ) def count_published_comments(url): return ( - db()((db().comment.url == url) & (db().comment.published is not None)).count() + db()((db().comment.url == url) & (db().comment.published != None)).count() if url - else db()(db().comment.published is not None).count() + else db()(db().comment.published != None).count() ) def find_recent_published_comments(): - return db()(db().comment.published is not None).select( + return db()(db().comment.published != None).select( orderby=~db().comment.published, limitby=(0, 10) ) diff --git a/stacosys/service/configuration.py b/stacosys/service/configuration.py index 67129a4..986409e 100644 --- a/stacosys/service/configuration.py +++ b/stacosys/service/configuration.py @@ -36,13 +36,12 @@ class Config: def load(self, config_pathname): self._cfg.read(config_pathname) - @staticmethod - def _split_key(key: ConfigParameter): + def _split_key(self, key: ConfigParameter): section, param = str(key.value).split(".") if not param: param = section section = "" - return section, param + return (section, param) def exists(self, key: ConfigParameter): section, param = self._split_key(key) @@ -79,8 +78,8 @@ class Config: def check(self): for key in ConfigParameter: if not self.get(key): - return False, key.value - return True, None + return (False, key.value) + return (True, None) def __repr__(self): dict_repr = {} diff --git a/stacosys/service/mail.py b/stacosys/service/mail.py index 8589e60..d7fe5ca 100644 --- a/stacosys/service/mail.py +++ b/stacosys/service/mail.py @@ -18,11 +18,11 @@ class Mailer: self._site_admin_email: str = "" def configure_smtp( - self, - smtp_host, - smtp_port, - smtp_login, - smtp_password, + self, + smtp_host, + smtp_port, + smtp_login, + smtp_password, ) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port From dbe6fd6b0cb6c657784a5409df091e7f3355a33a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:00:15 +0100 Subject: [PATCH 519/586] Upgrade deps --- poetry.lock | 200 ++++++++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1516292..9ad3cca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -100,101 +100,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -514,13 +514,13 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.5" +version = "3.5.1" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "Markdown-3.5-py3-none-any.whl", hash = "sha256:4afb124395ce5fc34e6d9886dab977fd9ae987fc6e85689f08278cf0c69d4bf3"}, - {file = "Markdown-3.5.tar.gz", hash = "sha256:a807eb2e4778d9156c8f07876c6e4d50b5494c5665c4834f67b06459dfd877b3"}, + {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, + {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, ] [package.dependencies] @@ -829,13 +829,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -978,13 +978,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.0" +version = "3.0.1" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, - {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, + {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, + {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, ] [package.dependencies] From d12625335474e73524c0718c743710c546497100 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:00:30 +0100 Subject: [PATCH 520/586] Improve unit test --- tests/test_db.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/test_db.py b/tests/test_db.py index a47e8c6..9b235b0 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -84,19 +84,12 @@ def create_comment(url, author_name, content): def test_find_recent_published_comments(setup_db): - comments = [] - comments.append(create_comment("/post", "Adam", "Comment 1")) - comments.append(create_comment("/post", "Arf", "Comment 2")) - comments.append(create_comment("/post", "Arwin", "Comment 3")) - comments.append(create_comment("/post", "Bill", "Comment 4")) - comments.append(create_comment("/post", "Bo", "Comment 5")) - comments.append(create_comment("/post", "Charles", "Comment 6")) - comments.append(create_comment("/post", "Dan", "Comment 7")) - comments.append(create_comment("/post", "Dwayne", "Comment 8")) - comments.append(create_comment("/post", "Erl", "Comment 9")) - comments.append(create_comment("/post", "Jay", "Comment 10")) - comments.append(create_comment("/post", "Kenny", "Comment 11")) - comments.append(create_comment("/post", "Lord", "Comment 12")) + comments = [create_comment("/post", "Adam", "Comment 1"), create_comment("/post", "Arf", "Comment 2"), + create_comment("/post", "Arwin", "Comment 3"), create_comment("/post", "Bill", "Comment 4"), + create_comment("/post", "Bo", "Comment 5"), create_comment("/post", "Charles", "Comment 6"), + create_comment("/post", "Dan", "Comment 7"), create_comment("/post", "Dwayne", "Comment 8"), + create_comment("/post", "Erl", "Comment 9"), create_comment("/post", "Jay", "Comment 10"), + create_comment("/post", "Kenny", "Comment 11"), create_comment("/post", "Lord", "Comment 12")] rows = dao.find_recent_published_comments() assert len(rows) == 0 From cd707f80083cccf806958c5bab405dc742d4c24a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:43:19 +0100 Subject: [PATCH 521/586] pyinstaller action --- .github/workflows/pyinstaller | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/pyinstaller diff --git a/.github/workflows/pyinstaller b/.github/workflows/pyinstaller new file mode 100644 index 0000000..3cf108e --- /dev/null +++ b/.github/workflows/pyinstaller @@ -0,0 +1,24 @@ +name: Package exe with PyInstaller - Windows + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: PyInstaller + uses: JackMcKew/pyinstaller-action-windows@python3-10-pyinstaller-5-3 + with: + path: . + + - uses: actions/upload-artifact@v2 + with: + name: my-exe + path: src/dist From c67c41a67d08ffce7b16bf3d4e00ea1839f1f43c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:44:17 +0100 Subject: [PATCH 522/586] Rename pyinstaller to pyinstaller.yml --- .github/workflows/{pyinstaller => pyinstaller.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{pyinstaller => pyinstaller.yml} (100%) diff --git a/.github/workflows/pyinstaller b/.github/workflows/pyinstaller.yml similarity index 100% rename from .github/workflows/pyinstaller rename to .github/workflows/pyinstaller.yml From 59171d27789aea014ea81afab275da2eebba5948 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:05:31 +0100 Subject: [PATCH 523/586] Update pyinstaller.yml --- .github/workflows/pyinstaller.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index 3cf108e..ffc0942 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -1,24 +1,19 @@ -name: Package exe with PyInstaller - Windows - +name: Package exe with PyInstaller on: push: branches: [ main ] pull_request: branches: [ main ] - jobs: build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - - name: PyInstaller - uses: JackMcKew/pyinstaller-action-windows@python3-10-pyinstaller-5-3 + - name: Package application + uses: JackMcKew/pyinstaller-action-linux@main with: path: . - - uses: actions/upload-artifact@v2 with: - name: my-exe - path: src/dist + name: stacosys + path: dist/linux From 1cd0698ab03c5e90de4fe717d328348e83eeaf1a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:08:37 +0100 Subject: [PATCH 524/586] Update pyinstaller.yml --- .github/workflows/pyinstaller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index ffc0942..497d054 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -1,4 +1,4 @@ -name: Package exe with PyInstaller +name: Package with PyInstaller on: push: branches: [ main ] @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Package application - uses: JackMcKew/pyinstaller-action-linux@main + uses: JackMcKew/pyinstaller-action-linux@python3.10 with: path: . - uses: actions/upload-artifact@v2 From 56b14b3dba30ed9a1dca2b69d45a3e1255040c72 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:16:25 +0100 Subject: [PATCH 525/586] Update pyinstaller.yml --- .github/workflows/pyinstaller.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index 497d054..1243e59 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -1,4 +1,4 @@ -name: Package with PyInstaller +name: pyinstaller on: push: branches: [ main ] From b57c4f1ae676613e09a87b9ea85ede90af397070 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:51:06 +0100 Subject: [PATCH 526/586] Init Vagrantfile to run pyinstaller on Debian 11 --- vagrant/Vagrantfile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 vagrant/Vagrantfile diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile new file mode 100644 index 0000000..8f91a53 --- /dev/null +++ b/vagrant/Vagrantfile @@ -0,0 +1,11 @@ +Vagrant.configure("2") do |config| + config.vm.box = "debian/bullseye64" + config.vm.provider :virtualbox do |vb| + end + + config.vm.define "master" do |master| + master.vm.provision "shell", inline: <<-SHELL + mkdir /home/vagrant/stacosys + SHELL + end +end \ No newline at end of file From 6d53fdecacc67f3c5062295a6f59e64649f6dfb7 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:45:35 +0100 Subject: [PATCH 527/586] Feature rye (#17) Migrate from Poetry to Rye Adaptat github actions Group build targets in makefile --- .github/workflows/docker.yml | 29 +- .github/workflows/pyinstaller.yml | 19 +- .github/workflows/pytest.yml | 36 +- Makefile | 37 +- vagrant/Vagrantfile => Vagrantfile | 3 + build.sh | 3 - flake8.ini | 4 - poetry.lock | 1098 ----------------- poetry.toml | 3 - pyproject.toml | 58 +- requirements-dev.lock | 53 + requirements.lock | 26 + run.sh | 3 - {stacosys => src/stacosys}/db/__init__.py | 1 - {stacosys => src/stacosys}/db/dao.py | 0 .../stacosys}/interface/__init__.py | 0 {stacosys => src/stacosys}/interface/api.py | 0 {stacosys => src/stacosys}/interface/form.py | 0 .../interface/templates/admin_fr.html | 0 .../interface/templates/login_fr.html | 0 .../stacosys}/interface/web/admin.py | 0 {stacosys => src/stacosys}/model/comment.py | 0 {stacosys => src/stacosys}/model/email.py | 0 {stacosys => src/stacosys}/run.py | 0 .../stacosys}/service/__init__.py | 0 .../stacosys}/service/configuration.py | 1 - {stacosys => src/stacosys}/service/mail.py | 0 {stacosys => src/stacosys}/service/rssfeed.py | 0 stacosys.spec | 15 +- stacosys/__init__.py | 0 tests/test_db.py | 21 +- whitelist.txt | 1 - 32 files changed, 208 insertions(+), 1203 deletions(-) rename vagrant/Vagrantfile => Vagrantfile (78%) delete mode 100755 build.sh delete mode 100644 flake8.ini delete mode 100644 poetry.lock delete mode 100644 poetry.toml create mode 100644 requirements-dev.lock create mode 100644 requirements.lock delete mode 100755 run.sh rename {stacosys => src/stacosys}/db/__init__.py (99%) rename {stacosys => src/stacosys}/db/dao.py (100%) rename {stacosys => src/stacosys}/interface/__init__.py (100%) rename {stacosys => src/stacosys}/interface/api.py (100%) rename {stacosys => src/stacosys}/interface/form.py (100%) rename {stacosys => src/stacosys}/interface/templates/admin_fr.html (100%) rename {stacosys => src/stacosys}/interface/templates/login_fr.html (100%) rename {stacosys => src/stacosys}/interface/web/admin.py (100%) rename {stacosys => src/stacosys}/model/comment.py (100%) rename {stacosys => src/stacosys}/model/email.py (100%) rename {stacosys => src/stacosys}/run.py (100%) rename {stacosys => src/stacosys}/service/__init__.py (100%) rename {stacosys => src/stacosys}/service/configuration.py (99%) rename {stacosys => src/stacosys}/service/mail.py (100%) rename {stacosys => src/stacosys}/service/rssfeed.py (100%) delete mode 100644 stacosys/__init__.py delete mode 100644 whitelist.txt diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bf4f989..4315b58 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,21 +3,22 @@ on: push: branches: [ main ] jobs: - build: - runs-on: ubuntu-latest + build_docker: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.10.13] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.0" - - name: Install poetry - uses: abatilo/actions-poetry@v2 - with: - poetry-version: 1.2.2 - - name: Install dependencies - run: poetry install - - name: Build project - run: poetry build + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up rye + uses: atu4403/setup-rye-multiOS@v1 + - name: Sync dependencies using rye + run: | + rye pin ${{ matrix.python-version }} + rye sync + rye build --wheel --out dist - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index 1243e59..f7799b5 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -2,13 +2,22 @@ name: pyinstaller on: push: branches: [ main ] - pull_request: - branches: [ main ] jobs: - build: - runs-on: ubuntu-latest + build_binary: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.10.13] steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up rye + uses: atu4403/setup-rye-multiOS@v1 + - name: Sync dependencies using rye + run: | + rye pin ${{ matrix.python-version }} + rye sync - name: Package application uses: JackMcKew/pyinstaller-action-linux@python3.10 with: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 81d2900..50c7cd3 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -2,30 +2,26 @@ name: pytest on: push jobs: - ci: - strategy: - fail-fast: false - matrix: - python-version: [3.11.0] - poetry-version: [1.2.2] - os: [ubuntu-latest, macos-latest, windows-latest] + test: runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.10.13] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install poetry - uses: abatilo/actions-poetry@v2 - with: - poetry-version: ${{ matrix.poetry-version }} - - name: Install dependencies - run: poetry install + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up rye + uses: atu4403/setup-rye-multiOS@v1 + - name: Sync dependencies using rye + run: | + rye pin ${{ matrix.python-version }} + rye sync - name: Pytest and Coverage run: | - poetry run coverage run -m --source=stacosys pytest tests - poetry run coverage report + rye run coverage run -m --source=stacosys pytest tests + rye run coverage report - name: Send report to Coveralls - run: poetry run coveralls + run: rye run coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/Makefile b/Makefile index e7377fc..8ab4938 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,35 @@ -all: black test typehint lint +ifeq (run,$(firstword $(MAKECMDGOALS))) + # use the rest as arguments for "run" + RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + # ...and turn them into do-nothing targets + $(eval $(RUN_ARGS):;@:) +endif + +.PHONY: all build run test + +# code quality +all: black typehint lint black: - poetry run isort --multi-line 3 --profile black stacosys/ tests/ - poetry run black --target-version py311 stacosys/ tests/ - -test: - poetry run coverage run -m --source=stacosys pytest - poetry run coverage report + rye run isort --multi-line 3 --profile black src/ tests/ + rye run black --target-version py311 src/ tests/ typehint: - poetry run mypy --ignore-missing-imports stacosys/ tests/ + rye run mypy --ignore-missing-imports src/ tests/ lint: - poetry run pylint stacosys/ + rye run pylint src/ +# test +test: + rye run coverage run -m --source=stacosys pytest tests + rye run coverage report + +# build +#rye run pyinstaller src/stacosys/run.py --name stacosys --onefile +build: + rye run pyinstaller --clean stacosys.spec + +# run +run: + rye run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file diff --git a/vagrant/Vagrantfile b/Vagrantfile similarity index 78% rename from vagrant/Vagrantfile rename to Vagrantfile index 8f91a53..9f6d773 100644 --- a/vagrant/Vagrantfile +++ b/Vagrantfile @@ -1,9 +1,12 @@ Vagrant.configure("2") do |config| config.vm.box = "debian/bullseye64" config.vm.provider :virtualbox do |vb| + vb.memory = 1024 + vb.cpus = 1 end config.vm.define "master" do |master| + master.vm.hostname = "master" master.vm.provision "shell", inline: <<-SHELL mkdir /home/vagrant/stacosys SHELL diff --git a/build.sh b/build.sh deleted file mode 100755 index 5da4ff8..0000000 --- a/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -#pyinstaller stacosys/run.py --name stacosys --onefile -poetry run pyinstaller stacosys.spec diff --git a/flake8.ini b/flake8.ini deleted file mode 100644 index 9b8e5e8..0000000 --- a/flake8.ini +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = E203 -spellcheck-targets=comments diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 9ad3cca..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1098 +0,0 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. - -[[package]] -name = "altgraph" -version = "0.17.4" -description = "Python graph (network) package" -optional = false -python-versions = "*" -files = [ - {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, - {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, -] - -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - -[[package]] -name = "background" -version = "0.2.1" -description = "It does what it says it does." -optional = false -python-versions = "*" -files = [ - {file = "background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802"}, - {file = "background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91"}, -] - -[[package]] -name = "black" -version = "22.12.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.7" -files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "blinker" -version = "1.6.3" -description = "Fast, simple object-to-object and broadcast signaling" -optional = false -python-versions = ">=3.7" -files = [ - {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, - {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, -] - -[[package]] -name = "certifi" -version = "2023.7.22" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[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 = "6.5.0" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "coveralls" -version = "3.3.1" -description = "Show coverage stats online via coveralls.io" -optional = false -python-versions = ">= 3.5" -files = [ - {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, - {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, -] - -[package.dependencies] -coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" -docopt = ">=0.6.1" -requests = ">=1.0.0" - -[package.extras] -yaml = ["PyYAML (>=3.10)"] - -[[package]] -name = "dill" -version = "0.3.7" -description = "serialize all of Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, - {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -optional = false -python-versions = "*" -files = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.1.3" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "flask" -version = "2.3.3" -description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.8" -files = [ - {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, - {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, -] - -[package.dependencies] -blinker = ">=1.6.2" -click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=2.3.7" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.12.0" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.7" -files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.9.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.7" -files = [ - {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, -] - -[[package]] -name = "macholib" -version = "1.16.3" -description = "Mach-O header analysis and editing" -optional = false -python-versions = "*" -files = [ - {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, - {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, -] - -[package.dependencies] -altgraph = ">=0.17" - -[[package]] -name = "markdown" -version = "3.5.1" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, - {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "0.991" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, -] - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "pefile" -version = "2023.2.7" -description = "Python PE parsing module" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, - {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, -] - -[[package]] -name = "platformdirs" -version = "3.11.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.7" -files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pydal" -version = "20221110.1" -description = "a pure Python Database Abstraction Layer (for python version 2.7 and 3.x)" -optional = false -python-versions = "*" -files = [ - {file = "pydal-20221110.1.tar.gz", hash = "sha256:7c3e891c70f8d8918e36276f210a1959bb7badf3b276f47191986ffcf5b6a390"}, -] - -[[package]] -name = "pyinstaller" -version = "5.13.2" -description = "PyInstaller bundles a Python application and all its dependencies into a single package." -optional = false -python-versions = "<3.13,>=3.7" -files = [ - {file = "pyinstaller-5.13.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:16cbd66b59a37f4ee59373a003608d15df180a0d9eb1a29ff3bfbfae64b23d0f"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8f6dd0e797ae7efdd79226f78f35eb6a4981db16c13325e962a83395c0ec7420"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_i686.whl", hash = "sha256:65133ed89467edb2862036b35d7c5ebd381670412e1e4361215e289c786dd4e6"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:7d51734423685ab2a4324ab2981d9781b203dcae42839161a9ee98bfeaabdade"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:2c2fe9c52cb4577a3ac39626b84cf16cf30c2792f785502661286184f162ae0d"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c63ef6133eefe36c4b2f4daf4cfea3d6412ece2ca218f77aaf967e52a95ac9b8"}, - {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:aadafb6f213549a5906829bb252e586e2cf72a7fbdb5731810695e6516f0ab30"}, - {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b2e1c7f5cceb5e9800927ddd51acf9cc78fbaa9e79e822c48b0ee52d9ce3c892"}, - {file = "pyinstaller-5.13.2-py3-none-win32.whl", hash = "sha256:421cd24f26144f19b66d3868b49ed673176765f92fa9f7914cd2158d25b6d17e"}, - {file = "pyinstaller-5.13.2-py3-none-win_amd64.whl", hash = "sha256:ddcc2b36052a70052479a9e5da1af067b4496f43686ca3cdda99f8367d0627e4"}, - {file = "pyinstaller-5.13.2-py3-none-win_arm64.whl", hash = "sha256:27cd64e7cc6b74c5b1066cbf47d75f940b71356166031deb9778a2579bb874c6"}, - {file = "pyinstaller-5.13.2.tar.gz", hash = "sha256:c8e5d3489c3a7cc5f8401c2d1f48a70e588f9967e391c3b06ddac1f685f8d5d2"}, -] - -[package.dependencies] -altgraph = "*" -macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} -pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2021.4" -pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} -setuptools = ">=42.0.0" - -[package.extras] -encryption = ["tinyaes (>=1.0.0)"] -hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] - -[[package]] -name = "pyinstaller-hooks-contrib" -version = "2023.10" -description = "Community maintained hooks for PyInstaller" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"}, - {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"}, -] - -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, -] -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyrss2gen" -version = "1.1" -description = "Generate RSS2 using a Python data structure" -optional = false -python-versions = "*" -files = [ - {file = "PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7"}, -] - -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.2" -description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, - {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.12.1" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, - {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, -] - -[[package]] -name = "types-markdown" -version = "3.5.0.0" -description = "Typing stubs for Markdown" -optional = false -python-versions = ">=3.7" -files = [ - {file = "types-Markdown-3.5.0.0.tar.gz", hash = "sha256:8c2f5526bba29feee24040d4694ced554d09ae46d1f1523b2b739558beabae42"}, - {file = "types_Markdown-3.5.0.0-py3-none-any.whl", hash = "sha256:f1ff987576f347ec83aca0c2ced45e64e1deb771e0a6189744480013ccb59c96"}, -] - -[[package]] -name = "typing-extensions" -version = "4.8.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, -] - -[[package]] -name = "urllib3" -version = "2.0.7" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.7" -files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "werkzeug" -version = "3.0.1" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, -] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.9.0,<3.12" -content-hash = "8bda48658818d8aa2efbc31e1475d1b35635edfbc4ef37ffeac9d77523d69d60" diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index be97f1e..0000000 --- a/poetry.toml +++ /dev/null @@ -1,3 +0,0 @@ -[virtualenvs] -in-project = true -prefer-active-python = true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 796fb44..a2cd284 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,30 +1,42 @@ -[tool.poetry] +[project] name = "stacosys" version = "3.3" description = "STAtic COmmenting SYStem" -authors = ["Yax"] +authors = [ + { name = "Yax" } +] readme = "README.md" +requires-python = ">= 3.8" +dependencies = [ + "pyrss2gen>=1.1", + "markdown>=3.5.1", + "requests>=2.31.0", + "background>=0.2.1", + "Flask>=3.0.0", + "types-markdown>=3.5.0.1", + "pydal>=20230521.1" +] -[tool.poetry.dependencies] -python = ">=3.9.0,<3.12" -pyrss2gen = "^1.1" -markdown = "^3.1.1" -requests = "^2.25.1" -coverage = "^6.5" -background = "^0.2.1" -Flask = "^2.1.1" -types-markdown = "^3.4.2.1" -pydal = "^20221110.1" - -[tool.poetry.group.dev.dependencies] -pylint = "^2.15" -mypy = "^0.991" -pytest = "^7.2.0" -coveralls = "^3.3.1" -pytest-cov = "^4.0.0" -black = "^22.10.0" -pyinstaller = "^5.9.0" +[tool.pytest.ini_options] +pythonpath = [ + "src" +] [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.rye] +managed = true +dev-dependencies = [ + "pylint>=3.0.2", + "mypy>=1.6.1", + "pytest>=7.4.3", + "coveralls>=3.3.1", + "pytest-cov>=4.1.0", + "black>=23.10.1", + "pyinstaller>=6.1.0", +] + +[tool.hatch.metadata] +allow-direct-references = true diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..0520afd --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,53 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false + +-e file:. +altgraph==0.17.4 +astroid==3.0.1 +background==0.2.1 +black==23.11.0 +blinker==1.7.0 +certifi==2023.7.22 +charset-normalizer==3.3.2 +click==8.1.7 +coverage==6.5.0 +coveralls==3.3.1 +dill==0.3.7 +docopt==0.6.2 +exceptiongroup==1.1.3 +flask==3.0.0 +idna==3.4 +iniconfig==2.0.0 +isort==5.12.0 +itsdangerous==2.1.2 +jinja2==3.1.2 +markdown==3.5.1 +markupsafe==2.1.3 +mccabe==0.7.0 +mypy==1.6.1 +mypy-extensions==1.0.0 +packaging==23.2 +pathspec==0.11.2 +platformdirs==3.11.0 +pluggy==1.3.0 +pydal==20230521.1 +pyinstaller==6.1.0 +pyinstaller-hooks-contrib==2023.10 +pylint==3.0.2 +pyrss2gen==1.1 +pytest==7.4.3 +pytest-cov==4.1.0 +requests==2.31.0 +tomli==2.0.1 +tomlkit==0.12.2 +types-markdown==3.5.0.1 +typing-extensions==4.8.0 +urllib3==2.0.7 +werkzeug==3.0.1 +# The following packages are considered to be unsafe in a requirements file: +setuptools==68.2.2 diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..01232a5 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,26 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false + +-e file:. +background==0.2.1 +blinker==1.7.0 +certifi==2023.7.22 +charset-normalizer==3.3.2 +click==8.1.7 +flask==3.0.0 +idna==3.4 +itsdangerous==2.1.2 +jinja2==3.1.2 +markdown==3.5.1 +markupsafe==2.1.3 +pydal==20230521.1 +pyrss2gen==1.1 +requests==2.31.0 +types-markdown==3.5.0.1 +urllib3==2.0.7 +werkzeug==3.0.1 diff --git a/run.sh b/run.sh deleted file mode 100755 index fb544ec..0000000 --- a/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -python3 stacosys/run.py "$@" - diff --git a/stacosys/db/__init__.py b/src/stacosys/db/__init__.py similarity index 99% rename from stacosys/db/__init__.py rename to src/stacosys/db/__init__.py index 01b2a36..fd23efe 100644 --- a/stacosys/db/__init__.py +++ b/src/stacosys/db/__init__.py @@ -5,7 +5,6 @@ from pydal import DAL, Field class Database: - db_dal = DAL() def configure(self, db_uri): diff --git a/stacosys/db/dao.py b/src/stacosys/db/dao.py similarity index 100% rename from stacosys/db/dao.py rename to src/stacosys/db/dao.py diff --git a/stacosys/interface/__init__.py b/src/stacosys/interface/__init__.py similarity index 100% rename from stacosys/interface/__init__.py rename to src/stacosys/interface/__init__.py diff --git a/stacosys/interface/api.py b/src/stacosys/interface/api.py similarity index 100% rename from stacosys/interface/api.py rename to src/stacosys/interface/api.py diff --git a/stacosys/interface/form.py b/src/stacosys/interface/form.py similarity index 100% rename from stacosys/interface/form.py rename to src/stacosys/interface/form.py diff --git a/stacosys/interface/templates/admin_fr.html b/src/stacosys/interface/templates/admin_fr.html similarity index 100% rename from stacosys/interface/templates/admin_fr.html rename to src/stacosys/interface/templates/admin_fr.html diff --git a/stacosys/interface/templates/login_fr.html b/src/stacosys/interface/templates/login_fr.html similarity index 100% rename from stacosys/interface/templates/login_fr.html rename to src/stacosys/interface/templates/login_fr.html diff --git a/stacosys/interface/web/admin.py b/src/stacosys/interface/web/admin.py similarity index 100% rename from stacosys/interface/web/admin.py rename to src/stacosys/interface/web/admin.py diff --git a/stacosys/model/comment.py b/src/stacosys/model/comment.py similarity index 100% rename from stacosys/model/comment.py rename to src/stacosys/model/comment.py diff --git a/stacosys/model/email.py b/src/stacosys/model/email.py similarity index 100% rename from stacosys/model/email.py rename to src/stacosys/model/email.py diff --git a/stacosys/run.py b/src/stacosys/run.py similarity index 100% rename from stacosys/run.py rename to src/stacosys/run.py diff --git a/stacosys/service/__init__.py b/src/stacosys/service/__init__.py similarity index 100% rename from stacosys/service/__init__.py rename to src/stacosys/service/__init__.py diff --git a/stacosys/service/configuration.py b/src/stacosys/service/configuration.py similarity index 99% rename from stacosys/service/configuration.py rename to src/stacosys/service/configuration.py index 986409e..ef348e9 100644 --- a/stacosys/service/configuration.py +++ b/src/stacosys/service/configuration.py @@ -30,7 +30,6 @@ class ConfigParameter(Enum): class Config: - _cfg = configparser.ConfigParser() def load(self, config_pathname): diff --git a/stacosys/service/mail.py b/src/stacosys/service/mail.py similarity index 100% rename from stacosys/service/mail.py rename to src/stacosys/service/mail.py diff --git a/stacosys/service/rssfeed.py b/src/stacosys/service/rssfeed.py similarity index 100% rename from stacosys/service/rssfeed.py rename to src/stacosys/service/rssfeed.py diff --git a/stacosys.spec b/stacosys.spec index 85a4e01..30a8b39 100644 --- a/stacosys.spec +++ b/stacosys.spec @@ -1,31 +1,24 @@ # -*- mode: python ; coding: utf-8 -*- -block_cipher = None - - a = Analysis( - ['stacosys/run.py'], - pathex=[], + ['src/stacosys/run.py'], + pathex=['src'], binaries=[], - datas=[('stacosys/interface/templates/*.html', 'stacosys/interface/templates/')], + datas=[('src/stacosys/interface/templates/*.html', 'src/stacosys/interface/templates/')], hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, noarchive=False, ) -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, a.binaries, - a.zipfiles, a.datas, [], name='stacosys', diff --git a/stacosys/__init__.py b/stacosys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_db.py b/tests/test_db.py index 9b235b0..f82b389 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -83,13 +83,20 @@ def create_comment(url, author_name, content): def test_find_recent_published_comments(setup_db): - - comments = [create_comment("/post", "Adam", "Comment 1"), create_comment("/post", "Arf", "Comment 2"), - create_comment("/post", "Arwin", "Comment 3"), create_comment("/post", "Bill", "Comment 4"), - create_comment("/post", "Bo", "Comment 5"), create_comment("/post", "Charles", "Comment 6"), - create_comment("/post", "Dan", "Comment 7"), create_comment("/post", "Dwayne", "Comment 8"), - create_comment("/post", "Erl", "Comment 9"), create_comment("/post", "Jay", "Comment 10"), - create_comment("/post", "Kenny", "Comment 11"), create_comment("/post", "Lord", "Comment 12")] + comments = [ + create_comment("/post", "Adam", "Comment 1"), + create_comment("/post", "Arf", "Comment 2"), + create_comment("/post", "Arwin", "Comment 3"), + create_comment("/post", "Bill", "Comment 4"), + create_comment("/post", "Bo", "Comment 5"), + create_comment("/post", "Charles", "Comment 6"), + create_comment("/post", "Dan", "Comment 7"), + create_comment("/post", "Dwayne", "Comment 8"), + create_comment("/post", "Erl", "Comment 9"), + create_comment("/post", "Jay", "Comment 10"), + create_comment("/post", "Kenny", "Comment 11"), + create_comment("/post", "Lord", "Comment 12"), + ] rows = dao.find_recent_published_comments() assert len(rows) == 0 diff --git a/whitelist.txt b/whitelist.txt deleted file mode 100644 index a44b465..0000000 --- a/whitelist.txt +++ /dev/null @@ -1 +0,0 @@ -RSS \ No newline at end of file From 8ad46a798ccd078594e6cd3e82a7cf6d7374eec4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 19 Nov 2023 15:35:56 +0100 Subject: [PATCH 528/586] Add check target --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 8ab4938..a4492ea 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,9 @@ typehint: lint: rye run pylint src/ +# check +check: all + # test test: rye run coverage run -m --source=stacosys pytest tests From 22b7f83d82ddbd74a69dfbf314cfd73ef3dad883 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 19 Nov 2023 15:36:08 +0100 Subject: [PATCH 529/586] Remove Vagrantfile --- Vagrantfile | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 9f6d773..0000000 --- a/Vagrantfile +++ /dev/null @@ -1,14 +0,0 @@ -Vagrant.configure("2") do |config| - config.vm.box = "debian/bullseye64" - config.vm.provider :virtualbox do |vb| - vb.memory = 1024 - vb.cpus = 1 - end - - config.vm.define "master" do |master| - master.vm.hostname = "master" - master.vm.provision "shell", inline: <<-SHELL - mkdir /home/vagrant/stacosys - SHELL - end -end \ No newline at end of file From 1959bb4f7f68911c76b3f54d0affbae720cd479a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 19 Nov 2023 16:36:37 +0100 Subject: [PATCH 530/586] Development mode --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a4492ea..9fe962f 100644 --- a/Makefile +++ b/Makefile @@ -35,4 +35,4 @@ build: # run run: - rye run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file + PYTHONPATH=src/ rye run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file From bed5eb0d114e01a9860d298df97baf841f98a1c1 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 19 Nov 2023 17:36:31 +0100 Subject: [PATCH 531/586] Clean gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 81aa1d1..8e21425 100644 --- a/.gitignore +++ b/.gitignore @@ -62,9 +62,6 @@ db.sqlite db.json node_modules comments.xml -stacosys/bin/ -stacosys/pyvenv.cfg -stacosys/lib64 .vscode/ .pytest_cache/ workspace.code-workspace From e2664310f32863fc2b6a598fa2c96e974b58a7fd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:06:01 +0100 Subject: [PATCH 532/586] fix pylintrc warnings --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 51832a5..75dae48 100644 --- a/.pylintrc +++ b/.pylintrc @@ -307,8 +307,8 @@ min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [FORMAT] From 9f1a408a7ada15571d20281e9fa79d962f1fef82 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:51:51 +0100 Subject: [PATCH 533/586] CQ --- dbmigration/create_empty_db.py | 2 +- dbmigration/migrate_from_1.1_to_2.0.py | 2 +- src/stacosys/db/dao.py | 2 +- src/stacosys/model/comment.py | 2 +- src/stacosys/model/email.py | 2 +- src/stacosys/run.py | 2 +- src/stacosys/service/configuration.py | 9 +++++---- src/stacosys/service/rssfeed.py | 2 +- tests/test_api.py | 2 +- tests/test_config.py | 2 +- tests/test_db.py | 2 +- tests/test_form.py | 2 +- tests/test_mail.py | 2 +- tests/test_rssfeed.py | 2 +- 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/dbmigration/create_empty_db.py b/dbmigration/create_empty_db.py index 50bd0f2..43a317c 100644 --- a/dbmigration/create_empty_db.py +++ b/dbmigration/create_empty_db.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import sqlite3 diff --git a/dbmigration/migrate_from_1.1_to_2.0.py b/dbmigration/migrate_from_1.1_to_2.0.py index f63112e..e4618c8 100644 --- a/dbmigration/migrate_from_1.1_to_2.0.py +++ b/dbmigration/migrate_from_1.1_to_2.0.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import sqlite3 diff --git a/src/stacosys/db/dao.py b/src/stacosys/db/dao.py index 7d9abdb..307e439 100644 --- a/src/stacosys/db/dao.py +++ b/src/stacosys/db/dao.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- # pylint: disable=singleton-comparison diff --git a/src/stacosys/model/comment.py b/src/stacosys/model/comment.py index 41688a5..999b5a4 100644 --- a/src/stacosys/model/comment.py +++ b/src/stacosys/model/comment.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- from dataclasses import dataclass diff --git a/src/stacosys/model/email.py b/src/stacosys/model/email.py index e67fecf..fad2336 100644 --- a/src/stacosys/model/email.py +++ b/src/stacosys/model/email.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- from dataclasses import dataclass diff --git a/src/stacosys/run.py b/src/stacosys/run.py index 9cf26cd..eb90a09 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import argparse diff --git a/src/stacosys/service/configuration.py b/src/stacosys/service/configuration.py index ef348e9..8cba173 100644 --- a/src/stacosys/service/configuration.py +++ b/src/stacosys/service/configuration.py @@ -35,12 +35,13 @@ class Config: def load(self, config_pathname): self._cfg.read(config_pathname) - def _split_key(self, key: ConfigParameter): + @staticmethod + def _split_key(key: ConfigParameter): section, param = str(key.value).split(".") if not param: param = section section = "" - return (section, param) + return section, param def exists(self, key: ConfigParameter): section, param = self._split_key(key) @@ -77,8 +78,8 @@ class Config: def check(self): for key in ConfigParameter: if not self.get(key): - return (False, key.value) - return (True, None) + return False, key.value + return True, None def __repr__(self): dict_repr = {} diff --git a/src/stacosys/service/rssfeed.py b/src/stacosys/service/rssfeed.py index dd3c3d5..069448f 100644 --- a/src/stacosys/service/rssfeed.py +++ b/src/stacosys/service/rssfeed.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- from datetime import datetime diff --git a/tests/test_api.py b/tests/test_api.py index 6badcbd..6e27120 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import json diff --git a/tests/test_config.py b/tests/test_config.py index ae173c2..057f052 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import pytest diff --git a/tests/test_db.py b/tests/test_db.py index f82b389..68d6f83 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import time diff --git a/tests/test_form.py b/tests/test_form.py index 9244483..d27b89e 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import logging diff --git a/tests/test_mail.py b/tests/test_mail.py index 9f9597f..992845f 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- import pytest diff --git a/tests/test_rssfeed.py b/tests/test_rssfeed.py index 021fc1e..e2a15b9 100644 --- a/tests/test_rssfeed.py +++ b/tests/test_rssfeed.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: UTF-8 -*- from stacosys.service import rss From 687d05eea4a0c86ce73c63cd0e5e80a787830871 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:38:52 +0100 Subject: [PATCH 534/586] Update deps --- requirements-dev.lock | 22 +++++++++++----------- requirements.lock | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 0520afd..d1c2a8a 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,16 +12,16 @@ astroid==3.0.1 background==0.2.1 black==23.11.0 blinker==1.7.0 -certifi==2023.7.22 +certifi==2023.11.17 charset-normalizer==3.3.2 click==8.1.7 coverage==6.5.0 coveralls==3.3.1 dill==0.3.7 docopt==0.6.2 -exceptiongroup==1.1.3 +exceptiongroup==1.2.0 flask==3.0.0 -idna==3.4 +idna==3.6 iniconfig==2.0.0 isort==5.12.0 itsdangerous==2.1.2 @@ -29,14 +29,14 @@ jinja2==3.1.2 markdown==3.5.1 markupsafe==2.1.3 mccabe==0.7.0 -mypy==1.6.1 +mypy==1.7.1 mypy-extensions==1.0.0 packaging==23.2 pathspec==0.11.2 -platformdirs==3.11.0 +platformdirs==4.1.0 pluggy==1.3.0 -pydal==20230521.1 -pyinstaller==6.1.0 +pydal==20231114.3 +pyinstaller==6.2.0 pyinstaller-hooks-contrib==2023.10 pylint==3.0.2 pyrss2gen==1.1 @@ -44,10 +44,10 @@ pytest==7.4.3 pytest-cov==4.1.0 requests==2.31.0 tomli==2.0.1 -tomlkit==0.12.2 -types-markdown==3.5.0.1 +tomlkit==0.12.3 +types-markdown==3.5.0.3 typing-extensions==4.8.0 -urllib3==2.0.7 +urllib3==2.1.0 werkzeug==3.0.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==68.2.2 +setuptools==69.0.2 diff --git a/requirements.lock b/requirements.lock index 01232a5..72723ee 100644 --- a/requirements.lock +++ b/requirements.lock @@ -9,18 +9,18 @@ -e file:. background==0.2.1 blinker==1.7.0 -certifi==2023.7.22 +certifi==2023.11.17 charset-normalizer==3.3.2 click==8.1.7 flask==3.0.0 -idna==3.4 +idna==3.6 itsdangerous==2.1.2 jinja2==3.1.2 markdown==3.5.1 markupsafe==2.1.3 -pydal==20230521.1 +pydal==20231114.3 pyrss2gen==1.1 requests==2.31.0 -types-markdown==3.5.0.1 -urllib3==2.0.7 +types-markdown==3.5.0.3 +urllib3==2.1.0 werkzeug==3.0.1 From 337a4f0fafc8e6a56a74eed932954e46b1a7ccad Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:31:46 +0100 Subject: [PATCH 535/586] new rye requirement --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a2cd284..c32426b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,3 +40,6 @@ dev-dependencies = [ [tool.hatch.metadata] allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["src"] From 2fb6f85c4ffce46081f6df998ddeaa15d121bc46 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:32:15 +0100 Subject: [PATCH 536/586] Upgrade deps --- requirements-dev.lock | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index d1c2a8a..8e15f8a 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,10 +7,14 @@ # all-features: false -e file:. +aiohttp==3.9.1 +aiosignal==1.3.1 altgraph==0.17.4 -astroid==3.0.1 +astroid==3.0.2 +async-timeout==4.0.3 +attrs==23.1.0 background==0.2.1 -black==23.11.0 +black==23.12.0 blinker==1.7.0 certifi==2023.11.17 charset-normalizer==3.3.2 @@ -21,24 +25,26 @@ dill==0.3.7 docopt==0.6.2 exceptiongroup==1.2.0 flask==3.0.0 +frozenlist==1.4.1 idna==3.6 iniconfig==2.0.0 -isort==5.12.0 +isort==5.13.2 itsdangerous==2.1.2 jinja2==3.1.2 markdown==3.5.1 markupsafe==2.1.3 mccabe==0.7.0 +multidict==6.0.4 mypy==1.7.1 mypy-extensions==1.0.0 packaging==23.2 -pathspec==0.11.2 +pathspec==0.12.1 platformdirs==4.1.0 pluggy==1.3.0 pydal==20231114.3 -pyinstaller==6.2.0 +pyinstaller==6.3.0 pyinstaller-hooks-contrib==2023.10 -pylint==3.0.2 +pylint==3.0.3 pyrss2gen==1.1 pytest==7.4.3 pytest-cov==4.1.0 @@ -46,8 +52,9 @@ requests==2.31.0 tomli==2.0.1 tomlkit==0.12.3 types-markdown==3.5.0.3 -typing-extensions==4.8.0 +typing-extensions==4.9.0 urllib3==2.1.0 werkzeug==3.0.1 +yarl==1.9.4 # The following packages are considered to be unsafe in a requirements file: setuptools==69.0.2 From 4b51391a28be0dee602269c46eb4dfda1e4c509b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:35:32 +0100 Subject: [PATCH 537/586] Update README: poetry replaced by rye --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67237e4..521db14 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,16 @@ Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-ba ### Installation -Build and Dependency management relies on [Poetry](https://python-poetry.org/), but you can also use [published releases](https://github.com/kianby/stacosys/releases) or [Docker image](https://hub.docker.com/r/kianby/stacosys). +Build and Dependency management relies on [Rye](https://rye-up.com/) but you can also use [Docker image](https://hub.docker.com/r/kianby/stacosys). Build executable with pyinstaller - poetry run pyinstaller stacosys.spec + make build + +Update dependencies and sync virtual env + + rye lock --update-all + rye sync ### Improvements From 872cf0691051f30f331d7b277631f39801132967 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Tue, 20 Feb 2024 21:59:30 +0100 Subject: [PATCH 538/586] Update deps --- requirements-dev.lock | 41 +++++++++++++++++------------------------ requirements.lock | 14 +++++++------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 8e15f8a..dc2cb8f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,54 +7,47 @@ # all-features: false -e file:. -aiohttp==3.9.1 -aiosignal==1.3.1 altgraph==0.17.4 -astroid==3.0.2 -async-timeout==4.0.3 -attrs==23.1.0 +astroid==3.0.3 background==0.2.1 -black==23.12.0 +black==24.2.0 blinker==1.7.0 -certifi==2023.11.17 +certifi==2024.2.2 charset-normalizer==3.3.2 click==8.1.7 coverage==6.5.0 coveralls==3.3.1 -dill==0.3.7 +dill==0.3.8 docopt==0.6.2 exceptiongroup==1.2.0 -flask==3.0.0 -frozenlist==1.4.1 +flask==3.0.2 idna==3.6 iniconfig==2.0.0 isort==5.13.2 itsdangerous==2.1.2 -jinja2==3.1.2 -markdown==3.5.1 -markupsafe==2.1.3 +jinja2==3.1.3 +markdown==3.5.2 +markupsafe==2.1.5 mccabe==0.7.0 -multidict==6.0.4 -mypy==1.7.1 +mypy==1.8.0 mypy-extensions==1.0.0 packaging==23.2 pathspec==0.12.1 -platformdirs==4.1.0 -pluggy==1.3.0 +platformdirs==4.2.0 +pluggy==1.4.0 pydal==20231114.3 -pyinstaller==6.3.0 -pyinstaller-hooks-contrib==2023.10 +pyinstaller==6.4.0 +pyinstaller-hooks-contrib==2024.1 pylint==3.0.3 pyrss2gen==1.1 -pytest==7.4.3 +pytest==8.0.1 pytest-cov==4.1.0 requests==2.31.0 tomli==2.0.1 tomlkit==0.12.3 -types-markdown==3.5.0.3 +types-markdown==3.5.0.20240129 typing-extensions==4.9.0 -urllib3==2.1.0 +urllib3==2.2.1 werkzeug==3.0.1 -yarl==1.9.4 # The following packages are considered to be unsafe in a requirements file: -setuptools==69.0.2 +setuptools==69.1.0 diff --git a/requirements.lock b/requirements.lock index 72723ee..edc27ec 100644 --- a/requirements.lock +++ b/requirements.lock @@ -9,18 +9,18 @@ -e file:. background==0.2.1 blinker==1.7.0 -certifi==2023.11.17 +certifi==2024.2.2 charset-normalizer==3.3.2 click==8.1.7 -flask==3.0.0 +flask==3.0.2 idna==3.6 itsdangerous==2.1.2 -jinja2==3.1.2 -markdown==3.5.1 -markupsafe==2.1.3 +jinja2==3.1.3 +markdown==3.5.2 +markupsafe==2.1.5 pydal==20231114.3 pyrss2gen==1.1 requests==2.31.0 -types-markdown==3.5.0.3 -urllib3==2.1.0 +types-markdown==3.5.0.20240129 +urllib3==2.2.1 werkzeug==3.0.1 From f47eb73a5cb759348b4810e2fe0561da3059b725 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:02:53 +0100 Subject: [PATCH 539/586] Sync upgrade --- requirements-dev.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index dc2cb8f..3f30eeb 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -8,7 +8,7 @@ -e file:. altgraph==0.17.4 -astroid==3.0.3 +astroid==3.1.0 background==0.2.1 black==24.2.0 blinker==1.7.0 @@ -38,16 +38,16 @@ pluggy==1.4.0 pydal==20231114.3 pyinstaller==6.4.0 pyinstaller-hooks-contrib==2024.1 -pylint==3.0.3 +pylint==3.1.0 pyrss2gen==1.1 -pytest==8.0.1 +pytest==8.0.2 pytest-cov==4.1.0 requests==2.31.0 tomli==2.0.1 -tomlkit==0.12.3 +tomlkit==0.12.4 types-markdown==3.5.0.20240129 -typing-extensions==4.9.0 +typing-extensions==4.10.0 urllib3==2.2.1 werkzeug==3.0.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==69.1.0 +setuptools==69.1.1 From 83aa8b1ba2d33f85cdf26c3b8d6dd3e22b55ece4 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:12:50 +0100 Subject: [PATCH 540/586] Upgrade flask --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c32426b..dec0886 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "markdown>=3.5.1", "requests>=2.31.0", "background>=0.2.1", - "Flask>=3.0.0", + "Flask>=3.0.2", "types-markdown>=3.5.0.1", "pydal>=20230521.1" ] From 885593db5b4a32431e49e16ba7d9f9443279e606 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:51:28 +0100 Subject: [PATCH 541/586] Improve code --- src/stacosys/run.py | 8 +++-- src/stacosys/service/mail.py | 60 ++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/stacosys/run.py b/src/stacosys/run.py index eb90a09..8e6f168 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -37,9 +37,9 @@ def stacosys_server(config_pathname): # load and check config config.load(config_pathname) - is_config_ok, erreur_config = config.check() + is_config_ok, config_error = config.check() if not is_config_ok: - logger.error("Configuration incorrecte '%s'", erreur_config) + logger.error("Invalid configuration '%s'", config_error) sys.exit(1) logger.info(config) @@ -65,7 +65,9 @@ def stacosys_server(config_pathname): config.get(ConfigParameter.SMTP_PASSWORD), ) mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) - mailer.check() + if not mailer.check(): + logger.error("Email configuration not working") + sys.exit(1) logger.info("start interfaces %s %s %s", api, form, admin) diff --git a/src/stacosys/service/mail.py b/src/stacosys/service/mail.py index d7fe5ca..ad3530c 100644 --- a/src/stacosys/service/mail.py +++ b/src/stacosys/service/mail.py @@ -2,44 +2,39 @@ # -*- coding: utf-8 -*- import logging -import smtplib -import ssl from email.mime.text import MIMEText +from smtplib import SMTP_SSL, SMTPAuthenticationError logger = logging.getLogger(__name__) class Mailer: def __init__(self) -> None: - self._smtp_host: str = "" - self._smtp_port: int = 0 - self._smtp_login: str = "" - self._smtp_password: str = "" - self._site_admin_email: str = "" + self._smtp_host = "" + self._smtp_port = 0 + self._smtp_login = "" + self._smtp_password = "" + self._site_admin_email = "" - def configure_smtp( - self, - smtp_host, - smtp_port, - smtp_login, - smtp_password, - ) -> None: + def configure_smtp(self, smtp_host: str, smtp_port: int, smtp_login: str, smtp_password: str) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_login = smtp_login self._smtp_password = smtp_password - def configure_destination(self, site_admin_email) -> None: + def configure_destination(self, site_admin_email: str) -> None: self._site_admin_email = site_admin_email - def check(self): - server = smtplib.SMTP_SSL( - self._smtp_host, self._smtp_port, context=ssl.create_default_context() - ) - server.login(self._smtp_login, self._smtp_password) - server.close() + def check(self) -> bool: + try: + with SMTP_SSL(self._smtp_host, self._smtp_port) as server: + server.login(self._smtp_login, self._smtp_password) + return True + except SMTPAuthenticationError: + logger.exception("Invalid credentials") + return False - def send(self, subject, message) -> bool: + def send(self, subject: str, message: str) -> bool: sender = self._smtp_login receivers = [self._site_admin_email] @@ -48,15 +43,14 @@ class Mailer: msg["To"] = self._site_admin_email msg["From"] = sender - # pylint: disable=bare-except try: - server = smtplib.SMTP_SSL( - self._smtp_host, self._smtp_port, context=ssl.create_default_context() - ) - server.login(self._smtp_login, self._smtp_password) - server.send_message(msg, sender, receivers) - server.close() - success = True - except: - success = False - return success + with SMTP_SSL(self._smtp_host, self._smtp_port) as server: + server.login(self._smtp_login, self._smtp_password) + server.send_message(msg, sender, receivers) + return True + except SMTPAuthenticationError: + logger.exception("Invalid credentials") + return False + except Exception as e: + logger.exception(f"Error sending email: {e}") + return False From 477477edae2851a67086b0447e425de5949b2f7f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:25:27 +0200 Subject: [PATCH 542/586] Refactor application startup. Use Flask app config and remove package singletons --- src/stacosys/interface/__init__.py | 7 +-- src/stacosys/interface/form.py | 3 +- src/stacosys/interface/web/admin.py | 15 +++-- src/stacosys/run.py | 87 ++++++++++++++++------------- src/stacosys/service/__init__.py | 10 ---- 5 files changed, 58 insertions(+), 64 deletions(-) delete mode 100644 src/stacosys/service/__init__.py diff --git a/src/stacosys/interface/__init__.py b/src/stacosys/interface/__init__.py index d72483d..c6211a4 100644 --- a/src/stacosys/interface/__init__.py +++ b/src/stacosys/interface/__init__.py @@ -7,7 +7,6 @@ import background from flask import Flask from stacosys.db import dao -from stacosys.service import config, mailer from stacosys.service.configuration import ConfigParameter app = Flask(__name__) @@ -20,7 +19,7 @@ logger = logging.getLogger(__name__) @background.task def submit_new_comment(comment): - site_url = config.get(ConfigParameter.SITE_URL) + site_url = app.config['CONFIG'].get(ConfigParameter.SITE_URL) comment_list = ( f"Web admin interface: {site_url}/web/admin", "", @@ -35,9 +34,9 @@ def submit_new_comment(comment): email_body = "\n".join(comment_list) # send email to notify admin - site_name = config.get(ConfigParameter.SITE_NAME) + site_name = app.config['CONFIG'].get(ConfigParameter.SITE_NAME) subject = f"STACOSYS {site_name}" - if mailer.send(subject, email_body): + if app.config['MAILER'].send(subject, email_body): logger.debug("new comment processed") # save notification datetime dao.notify_comment(comment) diff --git a/src/stacosys/interface/form.py b/src/stacosys/interface/form.py index 8513438..72e5e52 100644 --- a/src/stacosys/interface/form.py +++ b/src/stacosys/interface/form.py @@ -6,7 +6,6 @@ from flask import abort, redirect, request from stacosys.db import dao from stacosys.interface import app, submit_new_comment -from stacosys.service import config from stacosys.service.configuration import ConfigParameter logger = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def new_form_comment(): # send notification e-mail asynchronously submit_new_comment(comment) - return redirect(config.get(ConfigParameter.SITE_REDIRECT), code=302) + return redirect(app.config['CONFIG'].get(ConfigParameter.SITE_REDIRECT), code=302) def check_form_data(posted_comment): diff --git a/src/stacosys/interface/web/admin.py b/src/stacosys/interface/web/admin.py index 8d81e3c..4c8b9dd 100644 --- a/src/stacosys/interface/web/admin.py +++ b/src/stacosys/interface/web/admin.py @@ -8,7 +8,6 @@ from flask import flash, redirect, render_template, request, session from stacosys.db import dao from stacosys.interface import app -from stacosys.service import config, rss from stacosys.service.configuration import ConfigParameter logger = logging.getLogger(__name__) @@ -25,8 +24,8 @@ def index(): def is_login_ok(username, password): hashed = hashlib.sha256(password.encode()).hexdigest().upper() return ( - config.get(ConfigParameter.WEB_USERNAME) == username - and config.get(ConfigParameter.WEB_PASSWORD) == hashed + app.config['CONFIG'].get(ConfigParameter.WEB_USERNAME) == username + and app.config['CONFIG'].get(ConfigParameter.WEB_PASSWORD) == hashed ) @@ -42,7 +41,7 @@ def login(): flash("Identifiant ou mot de passe incorrect") return redirect("/web/login") # GET - return render_template("login_" + config.get(ConfigParameter.LANG) + ".html") + return render_template("login_" + app.config['CONFIG'].get(ConfigParameter.LANG) + ".html") @app.route("/web/logout", methods=["GET"]) @@ -55,7 +54,7 @@ def logout(): def admin_homepage(): if not ( "user" in session - and session["user"] == config.get(ConfigParameter.WEB_USERNAME) + and session["user"] == app.config['CONFIG'].get(ConfigParameter.WEB_USERNAME) ): # TODO localization flash("Vous avez été déconnecté.") @@ -63,9 +62,9 @@ def admin_homepage(): comments = dao.find_not_published_comments() return render_template( - "admin_" + config.get(ConfigParameter.LANG) + ".html", + "admin_" + app.config['CONFIG'].get(ConfigParameter.LANG) + ".html", comments=comments, - baseurl=config.get(ConfigParameter.SITE_URL), + baseurl=app.config['CONFIG'].get(ConfigParameter.SITE_URL), ) @@ -77,7 +76,7 @@ def admin_action(): flash("Commentaire introuvable") elif request.form.get("action") == "APPROVE": dao.publish_comment(comment) - rss.generate() + app.config['RSS'].generate() # TODO localization flash("Commentaire publié") else: diff --git a/src/stacosys/run.py b/src/stacosys/run.py index 8e6f168..ae7b81b 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -9,55 +9,34 @@ import sys from stacosys.db import database from stacosys.interface import api, app, form from stacosys.interface.web import admin -from stacosys.service import config, mailer, rss -from stacosys.service.configuration import ConfigParameter +from stacosys.service.mail import Mailer +from stacosys.service.rssfeed import Rss +from stacosys.service.configuration import Config, ConfigParameter # configure logging -def configure_logging(level): - root_logger = logging.getLogger() - root_logger.setLevel(level) - handler = logging.StreamHandler() - handler.setLevel(level) - formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s") - handler.setFormatter(formatter) - root_logger.addHandler(handler) - - -def stacosys_server(config_pathname): - # configure logging +def configure_logging() -> logging.Logger: + logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(name)s %(levelname)s %(message)s") logger = logging.getLogger(__name__) - configure_logging(logging.INFO) logging.getLogger("werkzeug").level = logging.WARNING + return logger - # check config file exists + +def load_and_validate_config(config_pathname: str, logger: logging.Logger) -> Config: if not os.path.isfile(config_pathname): logger.error("Configuration file '%s' not found.", config_pathname) - sys.exit(1) + raise FileNotFoundError(f"Configuration file '{config_pathname}' not found.") - # load and check config + config = Config() config.load(config_pathname) - is_config_ok, config_error = config.check() - if not is_config_ok: - logger.error("Invalid configuration '%s'", config_error) - sys.exit(1) - logger.info(config) + if not config.check(): + raise ValueError(f"Invalid configuration '{config_pathname}'") + logger.info("Configuration loaded successfully.") + return config - # initialize database - database.configure(config.get(ConfigParameter.DB)) - logger.info("Start Stacosys application") - - # generate RSS - rss.configure( - config.get(ConfigParameter.RSS_FILE), - config.get(ConfigParameter.SITE_NAME), - config.get(ConfigParameter.SITE_PROTO), - config.get(ConfigParameter.SITE_URL), - ) - rss.generate() - - # configure mailer +def configure_and_validate_mailer(config, logger): + mailer = Mailer() mailer.configure_smtp( config.get(ConfigParameter.SMTP_HOST), config.get_int(ConfigParameter.SMTP_PORT), @@ -68,10 +47,34 @@ def stacosys_server(config_pathname): if not mailer.check(): logger.error("Email configuration not working") sys.exit(1) + return mailer + + +def configure_rss(config): + rss = Rss() + rss.configure( + config.get(ConfigParameter.RSS_FILE), + config.get(ConfigParameter.SITE_NAME), + config.get(ConfigParameter.SITE_PROTO), + config.get(ConfigParameter.SITE_URL), + ) + rss.generate() + return rss + + +def main(config_pathname): + logger = configure_logging() + config = load_and_validate_config(config_pathname, logger) + database.configure(config.get(ConfigParameter.DB)) + + logger.info("Start Stacosys application") + rss = configure_rss(config) + mailer = configure_and_validate_mailer(config, logger) logger.info("start interfaces %s %s %s", api, form, admin) - - # start Flask + app.config['CONFIG'] = config + app.config['MAILER'] = mailer + app.config['RSS'] = rss app.run( host=config.get(ConfigParameter.HTTP_HOST), port=config.get_int(ConfigParameter.HTTP_PORT), @@ -84,4 +87,8 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("config", help="config path name") args = parser.parse_args() - stacosys_server(args.config) + try: + main(args.config) + except Exception as e: + logging.error(f"Failed to start application: {e}") + sys.exit(1) diff --git a/src/stacosys/service/__init__.py b/src/stacosys/service/__init__.py deleted file mode 100644 index 6fcc80a..0000000 --- a/src/stacosys/service/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from .configuration import Config -from .mail import Mailer -from .rssfeed import Rss - -config = Config() -mailer = Mailer() -rss = Rss() From a18eaf2237452b9f4489a4ae9b73cb9f880a7fb5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:26:16 +0200 Subject: [PATCH 543/586] Upgrade dependencies --- requirements-dev.lock | 28 ++++++++++++++-------------- requirements.lock | 10 +++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 3f30eeb..a01488a 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,7 +10,7 @@ altgraph==0.17.4 astroid==3.1.0 background==0.2.1 -black==24.2.0 +black==24.3.0 blinker==1.7.0 certifi==2024.2.2 charset-normalizer==3.3.2 @@ -20,34 +20,34 @@ coveralls==3.3.1 dill==0.3.8 docopt==0.6.2 exceptiongroup==1.2.0 -flask==3.0.2 -idna==3.6 +flask==3.0.3 +idna==3.7 iniconfig==2.0.0 isort==5.13.2 itsdangerous==2.1.2 jinja2==3.1.3 -markdown==3.5.2 +markdown==3.6 markupsafe==2.1.5 mccabe==0.7.0 -mypy==1.8.0 +mypy==1.9.0 mypy-extensions==1.0.0 -packaging==23.2 +packaging==24.0 pathspec==0.12.1 platformdirs==4.2.0 pluggy==1.4.0 pydal==20231114.3 -pyinstaller==6.4.0 -pyinstaller-hooks-contrib==2024.1 +pyinstaller==6.5.0 +pyinstaller-hooks-contrib==2024.3 pylint==3.1.0 pyrss2gen==1.1 -pytest==8.0.2 -pytest-cov==4.1.0 +pytest==8.1.1 +pytest-cov==5.0.0 requests==2.31.0 tomli==2.0.1 tomlkit==0.12.4 -types-markdown==3.5.0.20240129 -typing-extensions==4.10.0 +types-markdown==3.6.0.20240316 +typing-extensions==4.11.0 urllib3==2.2.1 -werkzeug==3.0.1 +werkzeug==3.0.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==69.1.1 +setuptools==69.3.0 diff --git a/requirements.lock b/requirements.lock index edc27ec..fca2376 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,15 +12,15 @@ blinker==1.7.0 certifi==2024.2.2 charset-normalizer==3.3.2 click==8.1.7 -flask==3.0.2 -idna==3.6 +flask==3.0.3 +idna==3.7 itsdangerous==2.1.2 jinja2==3.1.3 -markdown==3.5.2 +markdown==3.6 markupsafe==2.1.5 pydal==20231114.3 pyrss2gen==1.1 requests==2.31.0 -types-markdown==3.5.0.20240129 +types-markdown==3.6.0.20240316 urllib3==2.2.1 -werkzeug==3.0.1 +werkzeug==3.0.2 From 537e50902738a2a8f6be5c0f9a3147e5ec1f2aee Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:35:15 +0200 Subject: [PATCH 544/586] Fix unit tests --- tests/test_config.py | 3 ++- tests/test_form.py | 6 ++++++ tests/test_mail.py | 3 ++- tests/test_rssfeed.py | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 057f052..ecee23f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,13 +3,14 @@ import pytest -from stacosys.service import config +from stacosys.service.configuration import Config from stacosys.service.configuration import ConfigParameter EXPECTED_DB = "sqlite://db.sqlite" EXPECTED_HTTP_PORT = 8080 EXPECTED_LANG = "fr" +config = Config() @pytest.fixture def init_config(): diff --git a/tests/test_form.py b/tests/test_form.py index d27b89e..875dbf6 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -7,6 +7,9 @@ import pytest from stacosys.db import database from stacosys.interface import app, form +from stacosys.service.configuration import Config +from stacosys.service.mail import Mailer +from stacosys.service.rssfeed import Rss @pytest.fixture @@ -14,6 +17,9 @@ def client(): logger = logging.getLogger(__name__) database.configure("sqlite:memory://db.sqlite") logger.info(f"start interface {form}") + app.config['CONFIG'] = Config() + app.config['MAILER'] = Mailer() + app.config['RSS'] = Rss() return app.test_client() diff --git a/tests/test_mail.py b/tests/test_mail.py index 992845f..69aa307 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -3,10 +3,11 @@ import pytest -from stacosys.service import mailer +from stacosys.service.mail import Mailer def test_configure_and_check(): + mailer = Mailer() mailer.configure_smtp("localhost", 2525, "admin", "admin") mailer.configure_destination("admin@mydomain.com") with pytest.raises(ConnectionRefusedError): diff --git a/tests/test_rssfeed.py b/tests/test_rssfeed.py index e2a15b9..5b102fe 100644 --- a/tests/test_rssfeed.py +++ b/tests/test_rssfeed.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- -from stacosys.service import rss +from stacosys.service.rssfeed import Rss def test_configure(): + rss = Rss() rss.configure("comments.xml", "blog", "http", "blog.mydomain.com") From 53316c2ce92343b8329fb8b03978bdcee3577b75 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:36:11 +0200 Subject: [PATCH 545/586] Black formatting --- src/stacosys/interface/__init__.py | 6 +++--- src/stacosys/interface/form.py | 2 +- src/stacosys/interface/web/admin.py | 16 +++++++++------- src/stacosys/run.py | 12 +++++++----- src/stacosys/service/mail.py | 4 +++- tests/test_config.py | 4 ++-- tests/test_form.py | 6 +++--- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/stacosys/interface/__init__.py b/src/stacosys/interface/__init__.py index c6211a4..fb1fbd1 100644 --- a/src/stacosys/interface/__init__.py +++ b/src/stacosys/interface/__init__.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) @background.task def submit_new_comment(comment): - site_url = app.config['CONFIG'].get(ConfigParameter.SITE_URL) + site_url = app.config["CONFIG"].get(ConfigParameter.SITE_URL) comment_list = ( f"Web admin interface: {site_url}/web/admin", "", @@ -34,9 +34,9 @@ def submit_new_comment(comment): email_body = "\n".join(comment_list) # send email to notify admin - site_name = app.config['CONFIG'].get(ConfigParameter.SITE_NAME) + site_name = app.config["CONFIG"].get(ConfigParameter.SITE_NAME) subject = f"STACOSYS {site_name}" - if app.config['MAILER'].send(subject, email_body): + if app.config["MAILER"].send(subject, email_body): logger.debug("new comment processed") # save notification datetime dao.notify_comment(comment) diff --git a/src/stacosys/interface/form.py b/src/stacosys/interface/form.py index 72e5e52..11d2ca3 100644 --- a/src/stacosys/interface/form.py +++ b/src/stacosys/interface/form.py @@ -46,7 +46,7 @@ def new_form_comment(): # send notification e-mail asynchronously submit_new_comment(comment) - return redirect(app.config['CONFIG'].get(ConfigParameter.SITE_REDIRECT), code=302) + return redirect(app.config["CONFIG"].get(ConfigParameter.SITE_REDIRECT), code=302) def check_form_data(posted_comment): diff --git a/src/stacosys/interface/web/admin.py b/src/stacosys/interface/web/admin.py index 4c8b9dd..64aa3da 100644 --- a/src/stacosys/interface/web/admin.py +++ b/src/stacosys/interface/web/admin.py @@ -24,8 +24,8 @@ def index(): def is_login_ok(username, password): hashed = hashlib.sha256(password.encode()).hexdigest().upper() return ( - app.config['CONFIG'].get(ConfigParameter.WEB_USERNAME) == username - and app.config['CONFIG'].get(ConfigParameter.WEB_PASSWORD) == hashed + app.config["CONFIG"].get(ConfigParameter.WEB_USERNAME) == username + and app.config["CONFIG"].get(ConfigParameter.WEB_PASSWORD) == hashed ) @@ -41,7 +41,9 @@ def login(): flash("Identifiant ou mot de passe incorrect") return redirect("/web/login") # GET - return render_template("login_" + app.config['CONFIG'].get(ConfigParameter.LANG) + ".html") + return render_template( + "login_" + app.config["CONFIG"].get(ConfigParameter.LANG) + ".html" + ) @app.route("/web/logout", methods=["GET"]) @@ -54,7 +56,7 @@ def logout(): def admin_homepage(): if not ( "user" in session - and session["user"] == app.config['CONFIG'].get(ConfigParameter.WEB_USERNAME) + and session["user"] == app.config["CONFIG"].get(ConfigParameter.WEB_USERNAME) ): # TODO localization flash("Vous avez été déconnecté.") @@ -62,9 +64,9 @@ def admin_homepage(): comments = dao.find_not_published_comments() return render_template( - "admin_" + app.config['CONFIG'].get(ConfigParameter.LANG) + ".html", + "admin_" + app.config["CONFIG"].get(ConfigParameter.LANG) + ".html", comments=comments, - baseurl=app.config['CONFIG'].get(ConfigParameter.SITE_URL), + baseurl=app.config["CONFIG"].get(ConfigParameter.SITE_URL), ) @@ -76,7 +78,7 @@ def admin_action(): flash("Commentaire introuvable") elif request.form.get("action") == "APPROVE": dao.publish_comment(comment) - app.config['RSS'].generate() + app.config["RSS"].generate() # TODO localization flash("Commentaire publié") else: diff --git a/src/stacosys/run.py b/src/stacosys/run.py index ae7b81b..5634764 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -9,14 +9,16 @@ import sys from stacosys.db import database from stacosys.interface import api, app, form from stacosys.interface.web import admin +from stacosys.service.configuration import Config, ConfigParameter from stacosys.service.mail import Mailer from stacosys.service.rssfeed import Rss -from stacosys.service.configuration import Config, ConfigParameter # configure logging def configure_logging() -> logging.Logger: - logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="[%(asctime)s] %(name)s %(levelname)s %(message)s" + ) logger = logging.getLogger(__name__) logging.getLogger("werkzeug").level = logging.WARNING return logger @@ -72,9 +74,9 @@ def main(config_pathname): mailer = configure_and_validate_mailer(config, logger) logger.info("start interfaces %s %s %s", api, form, admin) - app.config['CONFIG'] = config - app.config['MAILER'] = mailer - app.config['RSS'] = rss + app.config["CONFIG"] = config + app.config["MAILER"] = mailer + app.config["RSS"] = rss app.run( host=config.get(ConfigParameter.HTTP_HOST), port=config.get_int(ConfigParameter.HTTP_PORT), diff --git a/src/stacosys/service/mail.py b/src/stacosys/service/mail.py index ad3530c..4e52228 100644 --- a/src/stacosys/service/mail.py +++ b/src/stacosys/service/mail.py @@ -16,7 +16,9 @@ class Mailer: self._smtp_password = "" self._site_admin_email = "" - def configure_smtp(self, smtp_host: str, smtp_port: int, smtp_login: str, smtp_password: str) -> None: + def configure_smtp( + self, smtp_host: str, smtp_port: int, smtp_login: str, smtp_password: str + ) -> None: self._smtp_host = smtp_host self._smtp_port = smtp_port self._smtp_login = smtp_login diff --git a/tests/test_config.py b/tests/test_config.py index ecee23f..78a2da4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,8 +3,7 @@ import pytest -from stacosys.service.configuration import Config -from stacosys.service.configuration import ConfigParameter +from stacosys.service.configuration import Config, ConfigParameter EXPECTED_DB = "sqlite://db.sqlite" EXPECTED_HTTP_PORT = 8080 @@ -12,6 +11,7 @@ EXPECTED_LANG = "fr" config = Config() + @pytest.fixture def init_config(): config.put(ConfigParameter.DB, EXPECTED_DB) diff --git a/tests/test_form.py b/tests/test_form.py index 875dbf6..f87a172 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -17,9 +17,9 @@ def client(): logger = logging.getLogger(__name__) database.configure("sqlite:memory://db.sqlite") logger.info(f"start interface {form}") - app.config['CONFIG'] = Config() - app.config['MAILER'] = Mailer() - app.config['RSS'] = Rss() + app.config["CONFIG"] = Config() + app.config["MAILER"] = Mailer() + app.config["RSS"] = Rss() return app.test_client() From d8a49f2be8c36986cc323782d62cb0aab655ea4b Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 16:52:42 +0200 Subject: [PATCH 546/586] Lint --- src/stacosys/service/rssfeed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stacosys/service/rssfeed.py b/src/stacosys/service/rssfeed.py index 069448f..9c9ebe0 100644 --- a/src/stacosys/service/rssfeed.py +++ b/src/stacosys/service/rssfeed.py @@ -52,5 +52,5 @@ class Rss: lastBuildDate=datetime.now(), items=items, ) - # pylint: disable=consider-using-with - rss.write_xml(open(self._rss_file, "w", encoding="utf-8"), encoding="utf-8") + with open(self._rss_file, "w", encoding="utf-8") as outfile: + rss.write_xml(outfile, encoding="utf-8") From 708b84987c5c260f4ed700ce5bf55f8aa39377fd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 16:54:11 +0200 Subject: [PATCH 547/586] Lint --- src/stacosys/service/mail.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/stacosys/service/mail.py b/src/stacosys/service/mail.py index 4e52228..6aa9470 100644 --- a/src/stacosys/service/mail.py +++ b/src/stacosys/service/mail.py @@ -38,21 +38,22 @@ class Mailer: def send(self, subject: str, message: str) -> bool: sender = self._smtp_login - receivers = [self._site_admin_email] - - msg = MIMEText(message) - msg["Subject"] = subject - msg["To"] = self._site_admin_email - msg["From"] = sender try: + msg = MIMEText(message) + msg["Subject"] = subject + msg["From"] = sender + msg["To"] = self._site_admin_email + with SMTP_SSL(self._smtp_host, self._smtp_port) as server: - server.login(self._smtp_login, self._smtp_password) - server.send_message(msg, sender, receivers) + try: + server.login(self._smtp_login, self._smtp_password) + except SMTPAuthenticationError: + logger.exception("Invalid credentials") + return False + + server.send_message(msg) return True - except SMTPAuthenticationError: - logger.exception("Invalid credentials") - return False - except Exception as e: - logger.exception(f"Error sending email: {e}") - return False + except Exception: + logger.error("Error sending email", exc_info=True) + return False From 4d52229e4d769fd6e92e2a54f567d0fa0b7b4d67 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 16:54:25 +0200 Subject: [PATCH 548/586] Add EN translations --- .../interface/templates/admin_en.html | 64 +++++++++++++++++++ .../interface/templates/login_en.html | 42 ++++++++++++ src/stacosys/interface/web/admin.py | 30 ++++++--- 3 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 src/stacosys/interface/templates/admin_en.html create mode 100644 src/stacosys/interface/templates/login_en.html diff --git a/src/stacosys/interface/templates/admin_en.html b/src/stacosys/interface/templates/admin_en.html new file mode 100644 index 0000000..59c8f19 --- /dev/null +++ b/src/stacosys/interface/templates/admin_en.html @@ -0,0 +1,64 @@ + + + + + +Stacosys Comment Moderation + + + +
    +

    Comment Moderation

    + +
    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +

    {{ message }}

    + {% endfor %} +
    + {% endif %} + {% endwith %} + + + + + + + + + + + + {% for comment in comments %} + + + + + + + + {% endfor %} + +
    DateAuthorCommentArticleActions
    {{ comment.created }}{{ comment.author_name }}{{ comment.content }}{{ comment.url }} +
    + + + +
    +
    + + + +
    +
    +
    +
    +

    This page was designed by Yax with Simple.css.

    +
    + + diff --git a/src/stacosys/interface/templates/login_en.html b/src/stacosys/interface/templates/login_en.html new file mode 100644 index 0000000..6d81754 --- /dev/null +++ b/src/stacosys/interface/templates/login_en.html @@ -0,0 +1,42 @@ + + + + + +Stacosys + + + + +
    +

    Comment Moderation Login

    +
    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +

    {{ message }}

    + {% endfor %} +
    + {% endif %} + {% endwith %} +
    +

    +

    +

    +

    + +
    +
    + + + diff --git a/src/stacosys/interface/web/admin.py b/src/stacosys/interface/web/admin.py index 64aa3da..9e35d50 100644 --- a/src/stacosys/interface/web/admin.py +++ b/src/stacosys/interface/web/admin.py @@ -37,8 +37,10 @@ def login(): if is_login_ok(username, password): session["user"] = username return redirect("/web/admin") - # TODO localization - flash("Identifiant ou mot de passe incorrect") + if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": + flash("Identifiant ou mot de passe incorrect") + else: + flash("Username or password incorrect") return redirect("/web/login") # GET return render_template( @@ -49,6 +51,10 @@ def login(): @app.route("/web/logout", methods=["GET"]) def logout(): session.pop("user") + if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": + flash("Vous avez été déconnecté.") + else: + flash("You have been logged out.") return redirect("/web/admin") @@ -58,8 +64,6 @@ def admin_homepage(): "user" in session and session["user"] == app.config["CONFIG"].get(ConfigParameter.WEB_USERNAME) ): - # TODO localization - flash("Vous avez été déconnecté.") return redirect("/web/login") comments = dao.find_not_published_comments() @@ -74,15 +78,21 @@ def admin_homepage(): def admin_action(): comment = dao.find_comment_by_id(request.form.get("comment")) if comment is None: - # TODO localization - flash("Commentaire introuvable") + if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": + flash("Commentaire introuvable") + else: + flash("Comment not found.") elif request.form.get("action") == "APPROVE": dao.publish_comment(comment) app.config["RSS"].generate() - # TODO localization - flash("Commentaire publié") + if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": + flash("Commentaire publié") + else: + flash("Comment published.") else: dao.delete_comment(comment) - # TODO localization - flash("Commentaire supprimé") + if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": + flash("Commentaire supprimé") + else: + flash("Comment deleted.") return redirect("/web/admin") From 69f1c35ef5b7707d6ff6de00ff7bedd33b697b9c Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 17:37:10 +0200 Subject: [PATCH 549/586] externalize localized application messages --- src/stacosys/i18n/messages.py | 17 ++++++++++++++++ src/stacosys/i18n/messages_en.properties | 6 ++++++ src/stacosys/i18n/messages_fr.properties | 6 ++++++ src/stacosys/interface/web/admin.py | 25 +++++------------------- src/stacosys/run.py | 9 +++++++++ 5 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 src/stacosys/i18n/messages.py create mode 100644 src/stacosys/i18n/messages_en.properties create mode 100644 src/stacosys/i18n/messages_fr.properties diff --git a/src/stacosys/i18n/messages.py b/src/stacosys/i18n/messages.py new file mode 100644 index 0000000..d64cbac --- /dev/null +++ b/src/stacosys/i18n/messages.py @@ -0,0 +1,17 @@ +import configparser +import os + + +class Messages: + def __init__(self): + self.property_dict = {} + + def load_messages(self, lang): + config = configparser.ConfigParser() + config.read(os.path.join(os.path.dirname(__file__), 'messages_' + lang + '.properties')) + + for key, value in config.items('messages'): + self.property_dict[key] = value + + def get(self, key): + return self.property_dict.get(key) diff --git a/src/stacosys/i18n/messages_en.properties b/src/stacosys/i18n/messages_en.properties new file mode 100644 index 0000000..85d02a7 --- /dev/null +++ b/src/stacosys/i18n/messages_en.properties @@ -0,0 +1,6 @@ +[messages] +login.failure.username=Username or password incorrect +logout.flash=You have been logged out. +admin.comment.notfound=Comment not found. +admin.comment.approved=Comment published. +admin.comment.deleted=Comment deleted. \ No newline at end of file diff --git a/src/stacosys/i18n/messages_fr.properties b/src/stacosys/i18n/messages_fr.properties new file mode 100644 index 0000000..9137863 --- /dev/null +++ b/src/stacosys/i18n/messages_fr.properties @@ -0,0 +1,6 @@ +[messages] +login.failure.username=Identifiant ou mot de passe incorrect +logout.flash=Vous avez t dconnect. +admin.comment.notfound=Commentaire introuvable +admin.comment.approved=Commentaire publi +admin.comment.deleted=Commentaire supprim \ No newline at end of file diff --git a/src/stacosys/interface/web/admin.py b/src/stacosys/interface/web/admin.py index 9e35d50..9b8c0ef 100644 --- a/src/stacosys/interface/web/admin.py +++ b/src/stacosys/interface/web/admin.py @@ -37,10 +37,7 @@ def login(): if is_login_ok(username, password): session["user"] = username return redirect("/web/admin") - if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": - flash("Identifiant ou mot de passe incorrect") - else: - flash("Username or password incorrect") + flash(app.config["MESSAGES"].get("login.failure.username")) return redirect("/web/login") # GET return render_template( @@ -51,10 +48,7 @@ def login(): @app.route("/web/logout", methods=["GET"]) def logout(): session.pop("user") - if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": - flash("Vous avez été déconnecté.") - else: - flash("You have been logged out.") + flash(app.config["MESSAGES"].get("logout.flash")) return redirect("/web/admin") @@ -78,21 +72,12 @@ def admin_homepage(): def admin_action(): comment = dao.find_comment_by_id(request.form.get("comment")) if comment is None: - if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": - flash("Commentaire introuvable") - else: - flash("Comment not found.") + flash(app.config["MESSAGES"].get("admin.comment.notfound")) elif request.form.get("action") == "APPROVE": dao.publish_comment(comment) app.config["RSS"].generate() - if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": - flash("Commentaire publié") - else: - flash("Comment published.") + flash(app.config["MESSAGES"].get("admin.comment.approved")) else: dao.delete_comment(comment) - if app.config["CONFIG"].get(ConfigParameter.LANG) == "fr": - flash("Commentaire supprimé") - else: - flash("Comment deleted.") + flash(app.config["MESSAGES"].get("admin.comment.deleted")) return redirect("/web/admin") diff --git a/src/stacosys/run.py b/src/stacosys/run.py index 5634764..255c424 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -7,6 +7,7 @@ import os import sys from stacosys.db import database +from stacosys.i18n.messages import Messages from stacosys.interface import api, app, form from stacosys.interface.web import admin from stacosys.service.configuration import Config, ConfigParameter @@ -64,6 +65,12 @@ def configure_rss(config): return rss +def configure_localization(config): + messages = Messages() + messages.load_messages(config.get(ConfigParameter.LANG)) + return messages + + def main(config_pathname): logger = configure_logging() config = load_and_validate_config(config_pathname, logger) @@ -72,11 +79,13 @@ def main(config_pathname): logger.info("Start Stacosys application") rss = configure_rss(config) mailer = configure_and_validate_mailer(config, logger) + messages = configure_localization(config) logger.info("start interfaces %s %s %s", api, form, admin) app.config["CONFIG"] = config app.config["MAILER"] = mailer app.config["RSS"] = rss + app.config["MESSAGES"] = messages app.run( host=config.get(ConfigParameter.HTTP_HOST), port=config.get_int(ConfigParameter.HTTP_PORT), From a2863474de6a82db8ea24367b146ca5bab5a88b6 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 17:47:33 +0200 Subject: [PATCH 550/586] Change rye action --- .github/workflows/docker.yml | 2 +- .github/workflows/pyinstaller.yml | 2 +- .github/workflows/pytest.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4315b58..891f435 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: atu4403/setup-rye-multiOS@v1 + uses: sksat/setup-rye@v0.8.0 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index f7799b5..442388d 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: atu4403/setup-rye-multiOS@v1 + uses: sksat/setup-rye@v0.8.0 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 50c7cd3..b0ae2e9 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: atu4403/setup-rye-multiOS@v1 + uses: sksat/setup-rye@v0.8.0 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} From 467060e49170677da9c255f51d4047a1fe5118cb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Fri, 31 May 2024 17:50:49 +0200 Subject: [PATCH 551/586] Use latest rye action version --- .github/workflows/docker.yml | 2 +- .github/workflows/pyinstaller.yml | 2 +- .github/workflows/pytest.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 891f435..6c429c1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: sksat/setup-rye@v0.8.0 + uses: sksat/setup-rye@v0.23.1 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index 442388d..7d1f54c 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: sksat/setup-rye@v0.8.0 + uses: sksat/setup-rye@v0.23.1 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b0ae2e9..0d1c233 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - name: Set up rye - uses: sksat/setup-rye@v0.8.0 + uses: sksat/setup-rye@v0.23.1 - name: Sync dependencies using rye run: | rye pin ${{ matrix.python-version }} From 8b132a71b387199050640dde378b046bbcea3871 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Sep 2024 14:50:13 +0200 Subject: [PATCH 552/586] Improve code quality --- .gitignore | 1 + src/stacosys/i18n/messages.py | 6 ++++-- src/stacosys/model/email.py | 32 -------------------------------- src/stacosys/run.py | 2 +- 4 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 src/stacosys/model/email.py diff --git a/.gitignore b/.gitignore index 8e21425..381b24b 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ config-dev.ini .python-version stacosys.sublime-project stacosys.sublime-workspace +out/ \ No newline at end of file diff --git a/src/stacosys/i18n/messages.py b/src/stacosys/i18n/messages.py index d64cbac..a92113a 100644 --- a/src/stacosys/i18n/messages.py +++ b/src/stacosys/i18n/messages.py @@ -8,9 +8,11 @@ class Messages: def load_messages(self, lang): config = configparser.ConfigParser() - config.read(os.path.join(os.path.dirname(__file__), 'messages_' + lang + '.properties')) + config.read( + os.path.join(os.path.dirname(__file__), "messages_" + lang + ".properties") + ) - for key, value in config.items('messages'): + for key, value in config.items("messages"): self.property_dict[key] = value def get(self, key): diff --git a/src/stacosys/model/email.py b/src/stacosys/model/email.py deleted file mode 100644 index fad2336..0000000 --- a/src/stacosys/model/email.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- - -from dataclasses import dataclass -from datetime import datetime -from typing import List - - -@dataclass -class Part: - content: str - content_type: str - - -@dataclass -class Attachment: - filename: str - content: str - content_type: str - - -@dataclass -class Email: - id: int - encoding: str - date: datetime - from_addr: str - to_addr: str - subject: str - parts: List[Part] - attachments: List[Attachment] - plain_text_content: str diff --git a/src/stacosys/run.py b/src/stacosys/run.py index 255c424..42d903c 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -101,5 +101,5 @@ if __name__ == "__main__": try: main(args.config) except Exception as e: - logging.error(f"Failed to start application: {e}") + logging.error("Failed to start application: %s", e) sys.exit(1) From 811984542488d43641d10cb45339a0e6fce601c0 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 15 Sep 2024 14:50:13 +0200 Subject: [PATCH 553/586] Upgrade dependencies --- requirements-dev.lock | 119 ++++++++++++++++++++++++++++++++---------- requirements.lock | 48 +++++++++++++---- 2 files changed, 128 insertions(+), 39 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index a01488a..940750b 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -5,49 +5,112 @@ # pre: false # features: [] # all-features: false +# with-sources: false +# generate-hashes: false +# universal: false -e file:. altgraph==0.17.4 -astroid==3.1.0 + # via pyinstaller +astroid==3.2.4 + # via pylint background==0.2.1 -black==24.3.0 -blinker==1.7.0 -certifi==2024.2.2 + # via stacosys +black==24.8.0 +blinker==1.8.2 + # via flask +certifi==2024.8.30 + # via requests charset-normalizer==3.3.2 + # via requests click==8.1.7 -coverage==6.5.0 -coveralls==3.3.1 + # via black + # via flask +coverage==7.6.1 + # via coveralls + # via pytest-cov +coveralls==4.0.1 dill==0.3.8 + # via pylint docopt==0.6.2 -exceptiongroup==1.2.0 + # via coveralls +exceptiongroup==1.2.2 + # via pytest flask==3.0.3 -idna==3.7 + # via stacosys +idna==3.9 + # via requests +importlib-metadata==8.5.0 + # via flask + # via markdown + # via pyinstaller + # via pyinstaller-hooks-contrib iniconfig==2.0.0 + # via pytest isort==5.13.2 -itsdangerous==2.1.2 -jinja2==3.1.3 -markdown==3.6 + # via pylint +itsdangerous==2.2.0 + # via flask +jinja2==3.1.4 + # via flask +markdown==3.7 + # via stacosys markupsafe==2.1.5 + # via jinja2 + # via werkzeug mccabe==0.7.0 -mypy==1.9.0 + # via pylint +mypy==1.11.2 mypy-extensions==1.0.0 -packaging==24.0 + # via black + # via mypy +packaging==24.1 + # via black + # via pyinstaller + # via pyinstaller-hooks-contrib + # via pytest pathspec==0.12.1 -platformdirs==4.2.0 -pluggy==1.4.0 -pydal==20231114.3 -pyinstaller==6.5.0 -pyinstaller-hooks-contrib==2024.3 -pylint==3.1.0 + # via black +platformdirs==4.3.3 + # via black + # via pylint +pluggy==1.5.0 + # via pytest +pydal==20240906.1 + # via stacosys +pyinstaller==6.10.0 +pyinstaller-hooks-contrib==2024.8 + # via pyinstaller +pylint==3.2.7 pyrss2gen==1.1 -pytest==8.1.1 + # via stacosys +pytest==8.3.3 + # via pytest-cov pytest-cov==5.0.0 -requests==2.31.0 +requests==2.32.3 + # via coveralls + # via stacosys +setuptools==74.1.2 + # via pyinstaller + # via pyinstaller-hooks-contrib tomli==2.0.1 -tomlkit==0.12.4 -types-markdown==3.6.0.20240316 -typing-extensions==4.11.0 -urllib3==2.2.1 -werkzeug==3.0.2 -# The following packages are considered to be unsafe in a requirements file: -setuptools==69.3.0 + # via black + # via coverage + # via mypy + # via pylint + # via pytest +tomlkit==0.13.2 + # via pylint +types-markdown==3.7.0.20240822 + # via stacosys +typing-extensions==4.12.2 + # via astroid + # via black + # via mypy + # via pylint +urllib3==2.2.3 + # via requests +werkzeug==3.0.4 + # via flask +zipp==3.20.2 + # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index fca2376..03d2992 100644 --- a/requirements.lock +++ b/requirements.lock @@ -5,22 +5,48 @@ # pre: false # features: [] # all-features: false +# with-sources: false +# generate-hashes: false +# universal: false -e file:. background==0.2.1 -blinker==1.7.0 -certifi==2024.2.2 + # via stacosys +blinker==1.8.2 + # via flask +certifi==2024.8.30 + # via requests charset-normalizer==3.3.2 + # via requests click==8.1.7 + # via flask flask==3.0.3 -idna==3.7 -itsdangerous==2.1.2 -jinja2==3.1.3 -markdown==3.6 + # via stacosys +idna==3.9 + # via requests +importlib-metadata==8.5.0 + # via flask + # via markdown +itsdangerous==2.2.0 + # via flask +jinja2==3.1.4 + # via flask +markdown==3.7 + # via stacosys markupsafe==2.1.5 -pydal==20231114.3 + # via jinja2 + # via werkzeug +pydal==20240906.1 + # via stacosys pyrss2gen==1.1 -requests==2.31.0 -types-markdown==3.6.0.20240316 -urllib3==2.2.1 -werkzeug==3.0.2 + # via stacosys +requests==2.32.3 + # via stacosys +types-markdown==3.7.0.20240822 + # via stacosys +urllib3==2.2.3 + # via requests +werkzeug==3.0.4 + # via flask +zipp==3.20.2 + # via importlib-metadata From 0f61666553df6c34715ece406e86ee8359429248 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 22 Sep 2024 19:15:32 +0200 Subject: [PATCH 554/586] Upgrade upload-artifact action --- .github/workflows/pyinstaller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml index 7d1f54c..7596c01 100644 --- a/.github/workflows/pyinstaller.yml +++ b/.github/workflows/pyinstaller.yml @@ -22,7 +22,7 @@ jobs: uses: JackMcKew/pyinstaller-action-linux@python3.10 with: path: . - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: - name: stacosys + name: stacosys-${{ matrix.runs-on }} path: dist/linux From fe21b9ac0e5cf23e2529b6fe5be4cfe7bd48e5cb Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 22 Sep 2024 19:42:29 +0200 Subject: [PATCH 555/586] Fix unicode messages --- src/stacosys/i18n/messages_fr.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stacosys/i18n/messages_fr.properties b/src/stacosys/i18n/messages_fr.properties index 9137863..7e18584 100644 --- a/src/stacosys/i18n/messages_fr.properties +++ b/src/stacosys/i18n/messages_fr.properties @@ -1,6 +1,6 @@ [messages] login.failure.username=Identifiant ou mot de passe incorrect -logout.flash=Vous avez t dconnect. +logout.flash=Vous avez \xe9t\xe9 d\xe9connect\xe9. admin.comment.notfound=Commentaire introuvable -admin.comment.approved=Commentaire publi -admin.comment.deleted=Commentaire supprim \ No newline at end of file +admin.comment.approved=Commentaire publi\xe9 +admin.comment.deleted=Commentaire supprim\xe9 \ No newline at end of file From dc776881e405693968e84651322d114242669100 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 22 Sep 2024 19:42:53 +0200 Subject: [PATCH 556/586] include property files in pyinstaller executable --- stacosys.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacosys.spec b/stacosys.spec index 30a8b39..7a25893 100644 --- a/stacosys.spec +++ b/stacosys.spec @@ -5,7 +5,7 @@ a = Analysis( ['src/stacosys/run.py'], pathex=['src'], binaries=[], - datas=[('src/stacosys/interface/templates/*.html', 'src/stacosys/interface/templates/')], + datas=[('src/stacosys/interface/templates/*.html', 'stacosys/interface/templates/'), ('src/stacosys/i18n/*.properties', 'stacosys/i18n/')], hiddenimports=[], hookspath=[], hooksconfig={}, From 25ed2f06e083337839034dddaae1b812303d2051 Mon Sep 17 00:00:00 2001 From: evidencebp Date: Sun, 24 Nov 2024 19:05:23 +0200 Subject: [PATCH 557/586] src\stacosys\service\mail.py broad-exception-caught Catching Exception might hide unexpected exceptions, like those that might be raised due to future modification. Therefore, it is recommended to narrow the exceptions. The method send of the class Mailer catches Exception in line 57. MIMEText does not raise exceptions (if not using attachments). See https://docs.python.org/3/library/email.mime.html Most code is handled in an inner exception handling. In order to catch exception from SMTP_SSL I used SMTPException See https://docs.python.org/3/library/smtplib.html --- src/stacosys/service/mail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stacosys/service/mail.py b/src/stacosys/service/mail.py index 6aa9470..be2ded7 100644 --- a/src/stacosys/service/mail.py +++ b/src/stacosys/service/mail.py @@ -3,7 +3,7 @@ import logging from email.mime.text import MIMEText -from smtplib import SMTP_SSL, SMTPAuthenticationError +from smtplib import SMTP_SSL, SMTPAuthenticationError, SMTPException logger = logging.getLogger(__name__) @@ -54,6 +54,6 @@ class Mailer: server.send_message(msg) return True - except Exception: + except SMTPException: logger.error("Error sending email", exc_info=True) return False From 5c51a18d0c337dcd9ebdff90ac7fa5bbdec9f2c5 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:58:42 +0100 Subject: [PATCH 558/586] Replace rye by uv and upgrade to python 3.13.1 --- .github/workflows/docker.yml | 16 +- Dockerfile | 4 +- Makefile | 18 +- pyproject.toml | 51 ++-- requirements-dev.lock | 116 -------- requirements.lock | 52 ---- uv.lock | 502 +++++++++++++++++++++++++++++++++++ 7 files changed, 539 insertions(+), 220 deletions(-) delete mode 100644 requirements-dev.lock delete mode 100644 requirements.lock create mode 100644 uv.lock diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6c429c1..6b67fc8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,17 +8,19 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.10.13] + python-version: [3.13.1] steps: - name: Checkout code uses: actions/checkout@v3 - - name: Set up rye - uses: sksat/setup-rye@v0.23.1 - - name: Sync dependencies using rye + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + - name: Sync dependencies using uv run: | - rye pin ${{ matrix.python-version }} - rye sync - rye build --wheel --out dist + uv python pin ${{ matrix.python-version }} + uv sync + uv build --wheel --out-dir dist - name: Build the Docker image run: | echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io diff --git a/Dockerfile b/Dockerfile index 7ea52cc..f3a448e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM python:3.11.0-alpine +FROM python:3.13.1-alpine3.20 -ARG STACOSYS_VERSION=3.3 +ARG STACOSYS_VERSION=3.4 ARG STACOSYS_FILENAME=stacosys-${STACOSYS_VERSION}-py3-none-any.whl RUN apk update && apk add bash && apk add wget diff --git a/Makefile b/Makefile index 9fe962f..386eb06 100644 --- a/Makefile +++ b/Makefile @@ -11,28 +11,28 @@ endif all: black typehint lint black: - rye run isort --multi-line 3 --profile black src/ tests/ - rye run black --target-version py311 src/ tests/ + uv run isort --multi-line 3 --profile black src/ tests/ + uv run black --target-version py311 src/ tests/ typehint: - rye run mypy --ignore-missing-imports src/ tests/ + uv run mypy --ignore-missing-imports src/ tests/ lint: - rye run pylint src/ + uv run pylint src/ # check check: all # test test: - rye run coverage run -m --source=stacosys pytest tests - rye run coverage report + PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest tests + uv run coverage report # build -#rye run pyinstaller src/stacosys/run.py --name stacosys --onefile build: - rye run pyinstaller --clean stacosys.spec + uv build --wheel --out-dir dist + docker build -t kianby/stacosys . # run run: - PYTHONPATH=src/ rye run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file + PYTHONPATH=src/ uv run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index dec0886..590c7f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,45 +1,28 @@ [project] name = "stacosys" -version = "3.3" +version = "3.4" description = "STAtic COmmenting SYStem" +readme = "README.md" authors = [ { name = "Yax" } ] -readme = "README.md" -requires-python = ">= 3.8" +requires-python = ">=3.13.1" dependencies = [ - "pyrss2gen>=1.1", - "markdown>=3.5.1", - "requests>=2.31.0", "background>=0.2.1", - "Flask>=3.0.2", - "types-markdown>=3.5.0.1", - "pydal>=20230521.1" + "flask>=3.1.0", + "markdown>=3.7", + "pydal>=20241204.1", + "pyrss2gen>=1.1", + "requests>=2.32.3", + "types-markdown>=3.7.0.20241204", ] -[tool.pytest.ini_options] -pythonpath = [ - "src" +[dependency-groups] +dev = [ + "coveralls>=4.0.1", + "mypy>=1.13.0", + "pylint>=3.3.2", + "pytest-cov>=6.0.0", + "pytest>=8.3.4", + "black>=24.10.0", ] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.rye] -managed = true -dev-dependencies = [ - "pylint>=3.0.2", - "mypy>=1.6.1", - "pytest>=7.4.3", - "coveralls>=3.3.1", - "pytest-cov>=4.1.0", - "black>=23.10.1", - "pyinstaller>=6.1.0", -] - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["src"] diff --git a/requirements-dev.lock b/requirements-dev.lock deleted file mode 100644 index 940750b..0000000 --- a/requirements-dev.lock +++ /dev/null @@ -1,116 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: false -# with-sources: false -# generate-hashes: false -# universal: false - --e file:. -altgraph==0.17.4 - # via pyinstaller -astroid==3.2.4 - # via pylint -background==0.2.1 - # via stacosys -black==24.8.0 -blinker==1.8.2 - # via flask -certifi==2024.8.30 - # via requests -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via black - # via flask -coverage==7.6.1 - # via coveralls - # via pytest-cov -coveralls==4.0.1 -dill==0.3.8 - # via pylint -docopt==0.6.2 - # via coveralls -exceptiongroup==1.2.2 - # via pytest -flask==3.0.3 - # via stacosys -idna==3.9 - # via requests -importlib-metadata==8.5.0 - # via flask - # via markdown - # via pyinstaller - # via pyinstaller-hooks-contrib -iniconfig==2.0.0 - # via pytest -isort==5.13.2 - # via pylint -itsdangerous==2.2.0 - # via flask -jinja2==3.1.4 - # via flask -markdown==3.7 - # via stacosys -markupsafe==2.1.5 - # via jinja2 - # via werkzeug -mccabe==0.7.0 - # via pylint -mypy==1.11.2 -mypy-extensions==1.0.0 - # via black - # via mypy -packaging==24.1 - # via black - # via pyinstaller - # via pyinstaller-hooks-contrib - # via pytest -pathspec==0.12.1 - # via black -platformdirs==4.3.3 - # via black - # via pylint -pluggy==1.5.0 - # via pytest -pydal==20240906.1 - # via stacosys -pyinstaller==6.10.0 -pyinstaller-hooks-contrib==2024.8 - # via pyinstaller -pylint==3.2.7 -pyrss2gen==1.1 - # via stacosys -pytest==8.3.3 - # via pytest-cov -pytest-cov==5.0.0 -requests==2.32.3 - # via coveralls - # via stacosys -setuptools==74.1.2 - # via pyinstaller - # via pyinstaller-hooks-contrib -tomli==2.0.1 - # via black - # via coverage - # via mypy - # via pylint - # via pytest -tomlkit==0.13.2 - # via pylint -types-markdown==3.7.0.20240822 - # via stacosys -typing-extensions==4.12.2 - # via astroid - # via black - # via mypy - # via pylint -urllib3==2.2.3 - # via requests -werkzeug==3.0.4 - # via flask -zipp==3.20.2 - # via importlib-metadata diff --git a/requirements.lock b/requirements.lock deleted file mode 100644 index 03d2992..0000000 --- a/requirements.lock +++ /dev/null @@ -1,52 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: false -# with-sources: false -# generate-hashes: false -# universal: false - --e file:. -background==0.2.1 - # via stacosys -blinker==1.8.2 - # via flask -certifi==2024.8.30 - # via requests -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via flask -flask==3.0.3 - # via stacosys -idna==3.9 - # via requests -importlib-metadata==8.5.0 - # via flask - # via markdown -itsdangerous==2.2.0 - # via flask -jinja2==3.1.4 - # via flask -markdown==3.7 - # via stacosys -markupsafe==2.1.5 - # via jinja2 - # via werkzeug -pydal==20240906.1 - # via stacosys -pyrss2gen==1.1 - # via stacosys -requests==2.32.3 - # via stacosys -types-markdown==3.7.0.20240822 - # via stacosys -urllib3==2.2.3 - # via requests -werkzeug==3.0.4 - # via flask -zipp==3.20.2 - # via importlib-metadata diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..59809a6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,502 @@ +version = 1 +requires-python = ">=3.13.1" + +[[package]] +name = "astroid" +version = "3.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/40/e028137cb19ed577001c76b91c5c50fee5a9c85099f45820b69385574ac5/astroid-3.3.6.tar.gz", hash = "sha256:6aaea045f938c735ead292204afdb977a36e989522b7833ef6fea94de743f442", size = 397452 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/d2/82c8ccef22ea873a2b0da9636e47d45137eeeb2fb9320c5dbbdd3627bab0/astroid-3.3.6-py3-none-any.whl", hash = "sha256:db676dc4f3ae6bfe31cda227dc60e03438378d7a896aec57422c95634e8d722f", size = 274644 }, +] + +[[package]] +name = "background" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/41/d6122c8e4bb280b2182098d77554d00016b6ffe54201cd3fac7f52fe9df2/background-0.2.1.tar.gz", hash = "sha256:4a5ed40b4a2a9f3340b1402862725d35016dc2490f95d89a2de47c3ddf215b91", size = 3141 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/45/01a33c692ce9f22214cad440f34704ed74e56b6f21d90e71aa595b3c2b72/background-0.2.1-py3-none-any.whl", hash = "sha256:c230e2813c773f93ecae54281ce6b1b425c895c24599cc203b7f137e4d7c4802", size = 2209 }, +] + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986 }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085 }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928 }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875 }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/d2/c25011f4d036cf7e8acbbee07a8e09e9018390aee25ba085596c4b83d510/coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", size = 801710 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/26/9abab6539d2191dbda2ce8c97b67d74cbfc966cc5b25abb880ffc7c459bc/coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", size = 207356 }, + { url = "https://files.pythonhosted.org/packages/44/da/d49f19402240c93453f606e660a6676a2a1fbbaa6870cc23207790aa9697/coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", size = 207614 }, + { url = "https://files.pythonhosted.org/packages/da/e6/93bb9bf85497816082ec8da6124c25efa2052bd4c887dd3b317b91990c9e/coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", size = 240129 }, + { url = "https://files.pythonhosted.org/packages/df/65/6a824b9406fe066835c1274a9949e06f084d3e605eb1a602727a27ec2fe3/coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", size = 237276 }, + { url = "https://files.pythonhosted.org/packages/9f/79/6c7a800913a9dd23ac8c8da133ebb556771a5a3d4df36b46767b1baffd35/coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", size = 239267 }, + { url = "https://files.pythonhosted.org/packages/57/e7/834d530293fdc8a63ba8ff70033d5182022e569eceb9aec7fc716b678a39/coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", size = 238887 }, + { url = "https://files.pythonhosted.org/packages/15/05/ec9d6080852984f7163c96984444e7cd98b338fd045b191064f943ee1c08/coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", size = 236970 }, + { url = "https://files.pythonhosted.org/packages/0a/d8/775937670b93156aec29f694ce37f56214ed7597e1a75b4083ee4c32121c/coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", size = 238831 }, + { url = "https://files.pythonhosted.org/packages/f4/58/88551cb7fdd5ec98cb6044e8814e38583436b14040a5ece15349c44c8f7c/coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", size = 210000 }, + { url = "https://files.pythonhosted.org/packages/b7/12/cfbf49b95120872785ff8d56ab1c7fe3970a65e35010c311d7dd35c5fd00/coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", size = 210753 }, + { url = "https://files.pythonhosted.org/packages/7c/68/c1cb31445599b04bde21cbbaa6d21b47c5823cdfef99eae470dfce49c35a/coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", size = 208091 }, + { url = "https://files.pythonhosted.org/packages/11/73/84b02c6b19c4a11eb2d5b5eabe926fb26c21c080e0852f5e5a4f01165f9e/coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", size = 208369 }, + { url = "https://files.pythonhosted.org/packages/de/e0/ae5d878b72ff26df2e994a5c5b1c1f6a7507d976b23beecb1ed4c85411ef/coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", size = 251089 }, + { url = "https://files.pythonhosted.org/packages/ab/9c/0aaac011aef95a93ef3cb2fba3fde30bc7e68a6635199ed469b1f5ea355a/coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", size = 246806 }, + { url = "https://files.pythonhosted.org/packages/f8/19/4d5d3ae66938a7dcb2f58cef3fa5386f838f469575b0bb568c8cc9e3a33d/coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", size = 249164 }, + { url = "https://files.pythonhosted.org/packages/b3/0b/4ee8a7821f682af9ad440ae3c1e379da89a998883271f088102d7ca2473d/coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", size = 248642 }, + { url = "https://files.pythonhosted.org/packages/8a/12/36ff1d52be18a16b4700f561852e7afd8df56363a5edcfb04cf26a0e19e0/coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", size = 246516 }, + { url = "https://files.pythonhosted.org/packages/43/d0/8e258f6c3a527c1655602f4f576215e055ac704de2d101710a71a2affac2/coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", size = 247783 }, + { url = "https://files.pythonhosted.org/packages/a9/0d/1e4a48d289429d38aae3babdfcadbf35ca36bdcf3efc8f09b550a845bdb5/coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", size = 210646 }, + { url = "https://files.pythonhosted.org/packages/26/74/b0729f196f328ac55e42b1e22ec2f16d8bcafe4b8158a26ec9f1cdd1d93e/coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", size = 211815 }, +] + +[[package]] +name = "coveralls" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "docopt" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/75/a454fb443eb6a053833f61603a432ffbd7dd6ae53a11159bacfadb9d6219/coveralls-4.0.1.tar.gz", hash = "sha256:7b2a0a2bcef94f295e3cf28dcc55ca40b71c77d1c2446b538e85f0f7bc21aa69", size = 12419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/e5/6708c75e2a4cfca929302d4d9b53b862c6dc65bd75e6933ea3d20016d41d/coveralls-4.0.1-py3-none-any.whl", hash = "sha256:7a6b1fa9848332c7b2221afb20f3df90272ac0167060f41b5fe90429b30b1809", size = 13599 }, +] + +[[package]] +name = "dill" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/43/86fe3f9e130c4137b0f1b50784dd70a5087b911fe07fa81e53e0c4c47fea/dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c", size = 187000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", size = 119418 }, +] + +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901 } + +[[package]] +name = "flask" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mypy" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, + { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, + { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, + { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, + { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, + { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pydal" +version = "20241204.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/78/7ddf9aacea5cd2e63423d278d26465c63ecdae87cf1c503d8fc1f7dfcfa5/pydal-20241204.1.tar.gz", hash = "sha256:1ba1f9e528b985e234f5b3acfd9d549998b44f7ed7ae747b9e8d4ad3047bf511", size = 623731 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/de/30f6ee6c8f333a00969fb4d5cd3c8cb8ca69feeeb2518d69b69d9bbe732b/pydal-20241204.1-py2.py3-none-any.whl", hash = "sha256:416f06de17ab0a5340e11195a0583abfe484eceb067cd3ab92208d3dc5aa7683", size = 246873 }, +] + +[[package]] +name = "pylint" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/d8/4471b2cb4ad18b4af717918c468209bd2bd5a02c52f60be5ee8a71b5af2c/pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01", size = 1516485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/55/5eaf6c415f6ddb09b9b039278823a8e27fb81ea7a34ec80c6d9223b17f2e/pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a", size = 521873 }, +] + +[[package]] +name = "pyrss2gen" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/01/fd610d5fc86f7dbdbefc4baa8f7fe15a2e5484244c41dcf363ca7e89f60c/PyRSS2Gen-1.1.tar.gz", hash = "sha256:7960aed7e998d2482bf58716c316509786f596426f879b05f8d84e98b82c6ee7", size = 6854 } + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "stacosys" +version = "3.4" +source = { virtual = "." } +dependencies = [ + { name = "background" }, + { name = "flask" }, + { name = "markdown" }, + { name = "pydal" }, + { name = "pyrss2gen" }, + { name = "requests" }, + { name = "types-markdown" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "coveralls" }, + { name = "mypy" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "background", specifier = ">=0.2.1" }, + { name = "flask", specifier = ">=3.1.0" }, + { name = "markdown", specifier = ">=3.7" }, + { name = "pydal", specifier = ">=20241204.1" }, + { name = "pyrss2gen", specifier = ">=1.1" }, + { name = "requests", specifier = ">=2.32.3" }, + { name = "types-markdown", specifier = ">=3.7.0.20241204" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=24.10.0" }, + { name = "coveralls", specifier = ">=4.0.1" }, + { name = "mypy", specifier = ">=1.13.0" }, + { name = "pylint", specifier = ">=3.3.2" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "types-markdown" +version = "3.7.0.20241204" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/3c/874ac6ce93f4e6bd0283a5df2c8065f4e623c6c3bc0b2fb98c098313cb73/types_markdown-3.7.0.20241204.tar.gz", hash = "sha256:ecca2b25cd23163fd28ed5ba34d183d731da03e8a5ed3a20b60daded304c5410", size = 17820 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/26/3c9730e845cfd0d587e0dfa9c1975f02f9f49407afbf30800094bdac0286/types_Markdown-3.7.0.20241204-py3-none-any.whl", hash = "sha256:f96146c367ea9c82bfe9903559d72706555cc2a1a3474c58ebba03b418ab18da", size = 23572 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] From a58b310998503da4079370297db1e02e00d50326 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:03:04 +0100 Subject: [PATCH 559/586] Adapt git actions --- .github/workflows/pyinstaller.yml | 28 ---------------------------- .github/workflows/pytest.yml | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/pyinstaller.yml diff --git a/.github/workflows/pyinstaller.yml b/.github/workflows/pyinstaller.yml deleted file mode 100644 index 7596c01..0000000 --- a/.github/workflows/pyinstaller.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: pyinstaller -on: - push: - branches: [ main ] -jobs: - build_binary: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.10.13] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up rye - uses: sksat/setup-rye@v0.23.1 - - name: Sync dependencies using rye - run: | - rye pin ${{ matrix.python-version }} - rye sync - - name: Package application - uses: JackMcKew/pyinstaller-action-linux@python3.10 - with: - path: . - - uses: actions/upload-artifact@v4 - with: - name: stacosys-${{ matrix.runs-on }} - path: dist/linux diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0d1c233..defd939 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -7,21 +7,23 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.10.13] + python-version: [3.13.1] steps: - name: Checkout code uses: actions/checkout@v3 - - name: Set up rye - uses: sksat/setup-rye@v0.23.1 - - name: Sync dependencies using rye + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + - name: Sync dependencies using uv run: | - rye pin ${{ matrix.python-version }} - rye sync + uv python pin ${{ matrix.python-version }} + uv sync - name: Pytest and Coverage run: | - rye run coverage run -m --source=stacosys pytest tests - rye run coverage report + PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest tests + uv run coverage report - name: Send report to Coveralls - run: rye run coveralls + run: uv run coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} From f5fc48e909a45ee53674328f8da715ef76d0961a Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:17:42 +0100 Subject: [PATCH 560/586] update readme --- README.md | 2 +- stacosys.spec | 37 ------------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 stacosys.spec diff --git a/README.md b/README.md index 521db14..44bfb70 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) - [![Python version](https://img.shields.io/badge/Python-3.11-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-2.1-green.svg)](https://flask.palletsprojects.com) + [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) [![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) diff --git a/stacosys.spec b/stacosys.spec deleted file mode 100644 index 7a25893..0000000 --- a/stacosys.spec +++ /dev/null @@ -1,37 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['src/stacosys/run.py'], - pathex=['src'], - binaries=[], - datas=[('src/stacosys/interface/templates/*.html', 'stacosys/interface/templates/'), ('src/stacosys/i18n/*.properties', 'stacosys/i18n/')], - hiddenimports=[], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='stacosys', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) From 9bd8b8b594f6ca2dbc89c895b579b849b19ebe89 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:18:23 +0100 Subject: [PATCH 561/586] Package resources --- Makefile | 2 ++ pyproject.toml | 13 +++++++++++++ src/stacosys/i18n/messages.py | 11 +++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 386eb06..9da87cf 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,8 @@ test: # build build: + # https://stackoverflow.com/questions/24347450/how-do-you-add-additional-files-to-a-wheel + rm -rf build/* dist/* *.egg-info uv build --wheel --out-dir dist docker build -t kianby/stacosys . diff --git a/pyproject.toml b/pyproject.toml index 590c7f6..dd0af66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,3 +26,16 @@ dev = [ "pytest>=8.3.4", "black>=24.10.0", ] + +[tool.setuptools] +package-dir = { "" = "src" } # Specify the root directory for packages +packages = ["stacosys"] + +[tool.setuptools.package-data] +# Include `.properties` and `.html` files in the specified directories +"stacosys.i18n" = ["*.properties"] +"stacosys.interface.templates" = ["*.html"] + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" diff --git a/src/stacosys/i18n/messages.py b/src/stacosys/i18n/messages.py index a92113a..375506f 100644 --- a/src/stacosys/i18n/messages.py +++ b/src/stacosys/i18n/messages.py @@ -1,6 +1,6 @@ import configparser import os - +import importlib.resources class Messages: def __init__(self): @@ -8,9 +8,12 @@ class Messages: def load_messages(self, lang): config = configparser.ConfigParser() - config.read( - os.path.join(os.path.dirname(__file__), "messages_" + lang + ".properties") - ) + + # Access the resource file within the package + with importlib.resources.open_text( + __package__, f"messages_{lang}.properties" + ) as file: + config.read_file(file) for key, value in config.items("messages"): self.property_dict[key] = value From 06349b5153c22dd29a29de0946055bf50ecf01bd Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:13:58 +0100 Subject: [PATCH 562/586] Build issue --- .github/workflows/docker.yml | 1 + Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6b67fc8..3bc31b2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,6 +20,7 @@ jobs: run: | uv python pin ${{ matrix.python-version }} uv sync + rm -rf build *.egg-info uv build --wheel --out-dir dist - name: Build the Docker image run: | diff --git a/Makefile b/Makefile index 386eb06..ed6bb65 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ test: # build build: + rm -rf build *.egg-info uv build --wheel --out-dir dist docker build -t kianby/stacosys . From e527fad7f02e87a5d92f93852e345935c729a229 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:23:28 +0100 Subject: [PATCH 563/586] Fix build --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6b67fc8..11592ab 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,6 +20,7 @@ jobs: run: | uv python pin ${{ matrix.python-version }} uv sync + rm -rf build/* dist/* *.egg-info uv build --wheel --out-dir dist - name: Build the Docker image run: | From c25ed334db69010501d345468f9215d9477da220 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:33:01 +0100 Subject: [PATCH 564/586] Fix docker build --- .github/workflows/docker.yml | 3 +-- Makefile | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 11592ab..c2fa8b8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,8 +19,7 @@ jobs: - name: Sync dependencies using uv run: | uv python pin ${{ matrix.python-version }} - uv sync - rm -rf build/* dist/* *.egg-info + uv sync uv build --wheel --out-dir dist - name: Build the Docker image run: | diff --git a/Makefile b/Makefile index 05606a7..9da87cf 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,6 @@ test: build: # https://stackoverflow.com/questions/24347450/how-do-you-add-additional-files-to-a-wheel rm -rf build/* dist/* *.egg-info ->>>>>>> upgrade-docker uv build --wheel --out-dir dist docker build -t kianby/stacosys . From 6becceeb61fa2d5f7d6eeff4e56cc3a446f3eeed Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:04:38 +0100 Subject: [PATCH 565/586] Enable debug --- docker/docker-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-init.sh b/docker/docker-init.sh index 7c51f8e..19660c8 100644 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -6,4 +6,4 @@ cp -f stacosys/run.py . python3 run.py /config/config.ini # catch for debug -#tail -f /dev/null \ No newline at end of file +tail -f /dev/null \ No newline at end of file From ce4e2b2b1b98853aa8a3d97908a87a1896446045 Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:10:47 +0100 Subject: [PATCH 566/586] Debug --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f3a448e..d534dd6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN chmod +x usr/local/bin/docker-init.sh RUN cd / COPY dist/${STACOSYS_FILENAME} / RUN python3 -m pip install ${STACOSYS_FILENAME} --target /stacosys -RUN rm -f ${STACOSYS_FILENAME} +#RUN rm -f ${STACOSYS_FILENAME} WORKDIR /stacosys CMD ["docker-init.sh"] From a5032ed440759639d745afa8df24e864b2c670fe Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:21:19 +0100 Subject: [PATCH 567/586] Fix docker build --- .github/workflows/docker.yml | 3 ++- Dockerfile | 2 +- docker/docker-init.sh | 2 +- uv.lock | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c2fa8b8..11592ab 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,7 +19,8 @@ jobs: - name: Sync dependencies using uv run: | uv python pin ${{ matrix.python-version }} - uv sync + uv sync + rm -rf build/* dist/* *.egg-info uv build --wheel --out-dir dist - name: Build the Docker image run: | diff --git a/Dockerfile b/Dockerfile index d534dd6..f3a448e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN chmod +x usr/local/bin/docker-init.sh RUN cd / COPY dist/${STACOSYS_FILENAME} / RUN python3 -m pip install ${STACOSYS_FILENAME} --target /stacosys -#RUN rm -f ${STACOSYS_FILENAME} +RUN rm -f ${STACOSYS_FILENAME} WORKDIR /stacosys CMD ["docker-init.sh"] diff --git a/docker/docker-init.sh b/docker/docker-init.sh index 19660c8..7c51f8e 100644 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -6,4 +6,4 @@ cp -f stacosys/run.py . python3 run.py /config/config.ini # catch for debug -tail -f /dev/null \ No newline at end of file +#tail -f /dev/null \ No newline at end of file diff --git a/uv.lock b/uv.lock index 59809a6..afb15b0 100644 --- a/uv.lock +++ b/uv.lock @@ -411,7 +411,7 @@ wheels = [ [[package]] name = "stacosys" version = "3.4" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "background" }, { name = "flask" }, From 678271d80d4333c0a48471d4d27562189d2392de Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:54:49 +0100 Subject: [PATCH 568/586] Disable github docker action for time being --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 9da87cf..e35d817 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,8 @@ build: rm -rf build/* dist/* *.egg-info uv build --wheel --out-dir dist docker build -t kianby/stacosys . + #docker login -u kianby + #docker push docker.io/kianby/stacosys:latest # run run: From c28251a158a103a9e50e02ed51bfb7adb7a44a1f Mon Sep 17 00:00:00 2001 From: Yax <1949284+kianby@users.noreply.github.com> Date: Sun, 23 Mar 2025 17:51:19 +0100 Subject: [PATCH 569/586] Update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44bfb70..833cd43 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +To be updated. Moved to CodeBerg + [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) @@ -58,4 +60,4 @@ Update dependencies and sync virtual env ### Improvements -Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. \ No newline at end of file +Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. From e429ee80307eeddad4fdcf027e9f0eaa2a6f19b7 Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 28 Mar 2025 20:34:45 +0100 Subject: [PATCH 570/586] Fix docker build --- Makefile | 8 ++++---- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index e35d817..0650450 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,11 @@ test: build: # https://stackoverflow.com/questions/24347450/how-do-you-add-additional-files-to-a-wheel rm -rf build/* dist/* *.egg-info + uv sync uv build --wheel --out-dir dist - docker build -t kianby/stacosys . - #docker login -u kianby - #docker push docker.io/kianby/stacosys:latest + docker build -t source.madyanne.fr/yax/stacosys . + docker push source.madyanne.fr/yax/stacosys # run run: - PYTHONPATH=src/ uv run python src/stacosys/run.py $(RUN_ARGS) \ No newline at end of file + PYTHONPATH=src/ uv run python src/stacosys/run.py $(RUN_ARGS) diff --git a/pyproject.toml b/pyproject.toml index dd0af66..e59cc7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dev = [ [tool.setuptools] package-dir = { "" = "src" } # Specify the root directory for packages -packages = ["stacosys"] +packages = ["stacosys", "stacosys.db", "stacosys.i18n", "stacosys.interface", "stacosys.interface.web", "stacosys.interface.templates", "stacosys.model", "stacosys.service"] [tool.setuptools.package-data] # Include `.properties` and `.html` files in the specified directories @@ -38,4 +38,4 @@ packages = ["stacosys"] [build-system] requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" +build-backend = "setuptools.build_meta" \ No newline at end of file From d4140bc83e94f78ee3bfc739cebc8244c734cb84 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 29 Mar 2025 19:48:29 +0100 Subject: [PATCH 571/586] Zaclys migration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 833cd43..160ab37 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -To be updated. Moved to CodeBerg +To be updated. Moved to ~CodeBerg~ Zaclys [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) From 030fd258d0ad0d71950dd1d6345571893d6f32bd Mon Sep 17 00:00:00 2001 From: Yax Date: Fri, 4 Apr 2025 20:00:17 +0200 Subject: [PATCH 572/586] UTF-8 messages --- src/stacosys/i18n/messages_fr.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stacosys/i18n/messages_fr.properties b/src/stacosys/i18n/messages_fr.properties index 7e18584..6486ffe 100644 --- a/src/stacosys/i18n/messages_fr.properties +++ b/src/stacosys/i18n/messages_fr.properties @@ -1,6 +1,6 @@ [messages] login.failure.username=Identifiant ou mot de passe incorrect -logout.flash=Vous avez \xe9t\xe9 d\xe9connect\xe9. +logout.flash=Vous avez été déconnecté. admin.comment.notfound=Commentaire introuvable -admin.comment.approved=Commentaire publi\xe9 -admin.comment.deleted=Commentaire supprim\xe9 \ No newline at end of file +admin.comment.approved=Commentaire publié +admin.comment.deleted=Commentaire supprimé \ No newline at end of file From fe995baed6e402b9283af2bdc251304b6b90d8d5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:28:31 +0200 Subject: [PATCH 573/586] Remove coveralls.io and generate coverage badge locally --- .github/workflows/docker.yml | 29 ----------------------------- .github/workflows/pytest.yml | 29 ----------------------------- Makefile | 3 +++ README.md | 17 +++++++---------- coverage.svg | 21 +++++++++++++++++++++ pyproject.toml | 3 ++- src/stacosys/i18n/messages.py | 3 ++- uv.lock | 27 ++++++++++++++++++++++++++- 8 files changed, 61 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/docker.yml delete mode 100644 .github/workflows/pytest.yml create mode 100644 coverage.svg diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 11592ab..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: docker -on: - push: - branches: [ main ] -jobs: - build_docker: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.13.1] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Install the latest version of uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" - - name: Sync dependencies using uv - run: | - uv python pin ${{ matrix.python-version }} - uv sync - rm -rf build/* dist/* *.egg-info - uv build --wheel --out-dir dist - - name: Build the Docker image - run: | - echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io - docker build . --file Dockerfile --tag docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest - docker push docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:latest diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml deleted file mode 100644 index defd939..0000000 --- a/.github/workflows/pytest.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: pytest -on: push - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.13.1] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Install the latest version of uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" - - name: Sync dependencies using uv - run: | - uv python pin ${{ matrix.python-version }} - uv sync - - name: Pytest and Coverage - run: | - PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest tests - uv run coverage report - - name: Send report to Coveralls - run: uv run coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/Makefile b/Makefile index 0650450..74573e7 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ check: all test: PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest tests uv run coverage report + uv run coverage-badge -f -o coverage.svg # build build: @@ -35,6 +36,8 @@ build: uv sync uv build --wheel --out-dir dist docker build -t source.madyanne.fr/yax/stacosys . + +publish: docker push source.madyanne.fr/yax/stacosys # run diff --git a/README.md b/README.md index 160ab37..45be15e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -To be updated. Moved to ~CodeBerg~ Zaclys - [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) [![Coverage Status](https://coveralls.io/repos/github/kianby/stacosys/badge.svg?branch=main)](https://coveralls.io/github/kianby/stacosys?branch=main) [![Build status - docker image](https://github.com/kianby/stacosys/workflows/docker/badge.svg)](https://hub.docker.com/r/kianby/stacosys) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/coverage.svg) ## Stacosys @@ -47,17 +45,16 @@ Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-ba ### Installation -Build and Dependency management relies on [Rye](https://rye-up.com/) but you can also use [Docker image](https://hub.docker.com/r/kianby/stacosys). +Build and Dependency management relies on [uv](https://docs.astral.sh/uv/) -Build executable with pyinstaller +Run tests and coverage + + make test + +Build docker image make build -Update dependencies and sync virtual env - - rye lock --update-all - rye sync - ### Improvements Stacosys fits my needs, and it manages comments on [my blog](https://blogduyax.madyanne.fr) for a while. I don't have any plan to make big changes, it's more a python playground for me. So I strongly encourage you to fork and enhance the project if you need additional features. diff --git a/coverage.svg b/coverage.svg new file mode 100644 index 0000000..6963b3e --- /dev/null +++ b/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 87% + 87% + + diff --git a/pyproject.toml b/pyproject.toml index e59cc7c..85a93b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ authors = [ requires-python = ">=3.13.1" dependencies = [ "background>=0.2.1", + "coverage-badge>=1.1.2", "flask>=3.1.0", "markdown>=3.7", "pydal>=20241204.1", @@ -38,4 +39,4 @@ packages = ["stacosys", "stacosys.db", "stacosys.i18n", "stacosys.interface", "s [build-system] requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" diff --git a/src/stacosys/i18n/messages.py b/src/stacosys/i18n/messages.py index 375506f..374d066 100644 --- a/src/stacosys/i18n/messages.py +++ b/src/stacosys/i18n/messages.py @@ -1,6 +1,7 @@ import configparser -import os import importlib.resources +import os + class Messages: def __init__(self): diff --git a/uv.lock b/uv.lock index afb15b0..aeef7a6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.13.1" [[package]] @@ -86,7 +87,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -130,6 +131,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/74/b0729f196f328ac55e42b1e22ec2f16d8bcafe4b8158a26ec9f1cdd1d93e/coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", size = 211815 }, ] +[[package]] +name = "coverage-badge" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/8f/e92b0a010c76b0da82709838b3f3ae9aec638d0c44dbfb1186a5751f5d2e/coverage_badge-1.1.2.tar.gz", hash = "sha256:fe7ed58a3b72dad85a553b64a99e963dea3847dcd0b8ddd2b38a00333618642c", size = 6335 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/3d/5642a1a06191b2e1e0f87a2e824e6d3eb7c32c589a68ed4d1dcbd3324d63/coverage_badge-1.1.2-py2.py3-none-any.whl", hash = "sha256:d8413ce51c91043a1692b943616b450868cbeeb0ea6a0c54a32f8318c9c96ff7", size = 6493 }, +] + [[package]] name = "coveralls" version = "4.0.1" @@ -408,12 +422,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "setuptools" +version = "78.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, +] + [[package]] name = "stacosys" version = "3.4" source = { editable = "." } dependencies = [ { name = "background" }, + { name = "coverage-badge" }, { name = "flask" }, { name = "markdown" }, { name = "pydal" }, @@ -435,6 +459,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "background", specifier = ">=0.2.1" }, + { name = "coverage-badge", specifier = ">=1.1.2" }, { name = "flask", specifier = ">=3.1.0" }, { name = "markdown", specifier = ">=3.7" }, { name = "pydal", specifier = ">=20241204.1" }, From 1f347237be90777fa1a3cf427b16d6a6390ba771 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:30:08 +0200 Subject: [PATCH 574/586] Fix coverage badge link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45be15e..3916997 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/coverage.svg) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/src/branch/main/coverage.svg) ## Stacosys From 21e26e021db1f0f004e7bee51b656cf2afdf288e Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:31:41 +0200 Subject: [PATCH 575/586] Fix badge link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3916997..05ecfc4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/src/branch/main/coverage.svg) +[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage.svg) ## Stacosys From 59740dc5bebc76298e5a95e0eb1a83dfe7b09286 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:53:10 +0200 Subject: [PATCH 576/586] Ad test badge --- .gitignore | 4 ++- Makefile | 7 +++-- README.md | 2 +- coverage-badge.svg | 1 + coverage.svg | 21 -------------- pyproject.toml | 3 +- tests-badge.svg | 1 + uv.lock | 70 ++++++++++++++++++++++++++++++++++++---------- 8 files changed, 67 insertions(+), 42 deletions(-) create mode 100644 coverage-badge.svg delete mode 100644 coverage.svg create mode 100644 tests-badge.svg diff --git a/.gitignore b/.gitignore index 381b24b..9d89a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,6 @@ config-dev.ini .python-version stacosys.sublime-project stacosys.sublime-workspace -out/ \ No newline at end of file +out/ +junit.xml +coverage.xml \ No newline at end of file diff --git a/Makefile b/Makefile index 74573e7..9e40e0f 100644 --- a/Makefile +++ b/Makefile @@ -25,9 +25,10 @@ check: all # test test: - PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest tests - uv run coverage report - uv run coverage-badge -f -o coverage.svg + PYTHONPATH=src/ uv run coverage run -m --source=stacosys pytest --junitxml=junit.xml tests + uv run genbadge tests -i junit.xml + uv run coverage xml + uv run genbadge coverage -i coverage.xml # build build: diff --git a/README.md b/README.md index 05ecfc4..b013672 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -[![Build Status - pytest](https://github.com/kianby/stacosys/workflows/pytest/badge.svg)](https://github.com/kianby/stacosys) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage.svg) +![Build Status - pytest](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/tests-badge.svg) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage-badge.svg) ## Stacosys diff --git a/coverage-badge.svg b/coverage-badge.svg new file mode 100644 index 0000000..c3d15a1 --- /dev/null +++ b/coverage-badge.svg @@ -0,0 +1 @@ +coverage: 86.82%coverage86.82% \ No newline at end of file diff --git a/coverage.svg b/coverage.svg deleted file mode 100644 index 6963b3e..0000000 --- a/coverage.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - coverage - coverage - 87% - 87% - - diff --git a/pyproject.toml b/pyproject.toml index 85a93b6..ff7649d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,8 +9,9 @@ authors = [ requires-python = ">=3.13.1" dependencies = [ "background>=0.2.1", - "coverage-badge>=1.1.2", + "defusedxml>=0.7.1", "flask>=3.1.0", + "genbadge>=1.1.2", "markdown>=3.7", "pydal>=20241204.1", "pyrss2gen>=1.1", diff --git a/tests-badge.svg b/tests-badge.svg new file mode 100644 index 0000000..29110b8 --- /dev/null +++ b/tests-badge.svg @@ -0,0 +1 @@ +tests: 19tests19 \ No newline at end of file diff --git a/uv.lock b/uv.lock index aeef7a6..154cad1 100644 --- a/uv.lock +++ b/uv.lock @@ -131,19 +131,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/74/b0729f196f328ac55e42b1e22ec2f16d8bcafe4b8158a26ec9f1cdd1d93e/coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", size = 211815 }, ] -[[package]] -name = "coverage-badge" -version = "1.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/8f/e92b0a010c76b0da82709838b3f3ae9aec638d0c44dbfb1186a5751f5d2e/coverage_badge-1.1.2.tar.gz", hash = "sha256:fe7ed58a3b72dad85a553b64a99e963dea3847dcd0b8ddd2b38a00333618642c", size = 6335 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/3d/5642a1a06191b2e1e0f87a2e824e6d3eb7c32c589a68ed4d1dcbd3324d63/coverage_badge-1.1.2-py2.py3-none-any.whl", hash = "sha256:d8413ce51c91043a1692b943616b450868cbeeb0ea6a0c54a32f8318c9c96ff7", size = 6493 }, -] - [[package]] name = "coveralls" version = "4.0.1" @@ -158,6 +145,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/e5/6708c75e2a4cfca929302d4d9b53b862c6dc65bd75e6933ea3d20016d41d/coveralls-4.0.1-py3-none-any.whl", hash = "sha256:7a6b1fa9848332c7b2221afb20f3df90272ac0167060f41b5fe90429b30b1809", size = 13599 }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "dill" version = "0.3.9" @@ -189,6 +185,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 }, ] +[[package]] +name = "genbadge" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "pillow" }, + { name = "requests" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/2b/75c50383f951f36334635715819f89d1b4dae1de0ff7d510970bbf137994/genbadge-1.1.2.tar.gz", hash = "sha256:987ed2feaf6e9cc2850fc3883320d8706b3849eb6c9f436156254dcac438515c", size = 137188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/5e/91f2340d7a51ce0b7a59e5caa1cccd61131d8d5163cc02f3563c819cb49c/genbadge-1.1.2-py2.py3-none-any.whl", hash = "sha256:4e3073cb56c2745fbef4b7da97eb85b28a18a22af519b66acb6706b6546279f1", size = 100945 }, +] + [[package]] name = "idna" version = "3.10" @@ -328,6 +339,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] +[[package]] +name = "pillow" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, + { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, + { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, + { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, + { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, + { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, + { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, + { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, + { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, + { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, + { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, + { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, + { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, + { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, + { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -437,8 +475,9 @@ version = "3.4" source = { editable = "." } dependencies = [ { name = "background" }, - { name = "coverage-badge" }, + { name = "defusedxml" }, { name = "flask" }, + { name = "genbadge" }, { name = "markdown" }, { name = "pydal" }, { name = "pyrss2gen" }, @@ -459,8 +498,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "background", specifier = ">=0.2.1" }, - { name = "coverage-badge", specifier = ">=1.1.2" }, + { name = "defusedxml", specifier = ">=0.7.1" }, { name = "flask", specifier = ">=3.1.0" }, + { name = "genbadge", specifier = ">=1.1.2" }, { name = "markdown", specifier = ">=3.7" }, { name = "pydal", specifier = ">=20241204.1" }, { name = "pyrss2gen", specifier = ">=1.1" }, From fd6b9fa639f907647e45b0f13c619d9a7a33b12b Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:54:48 +0200 Subject: [PATCH 577/586] README update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b013672..6957a12 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -![Build Status - pytest](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/tests-badge.svg) ![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage-badge.svg) +![[Build Status - pytest](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/tests-badge.svg)]() ![[Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage-badge.svg)]() ## Stacosys From ccfd5804b08445f3d7a07fa1a879b700ab291ecb Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:55:40 +0200 Subject: [PATCH 578/586] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6957a12..71fe5c5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python version](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Flask version](https://img.shields.io/badge/Flask-3.1-green.svg)](https://flask.palletsprojects.com) -![[Build Status - pytest](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/tests-badge.svg)]() ![[Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage-badge.svg)]() +[![Build Status - pytest](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/tests-badge.svg)]() [![Coverage Status](https://gitea.zaclys.com/yannic/stacosys/raw/branch/main/coverage-badge.svg)]() ## Stacosys From ee09cfaf4054eed73c21400bf5acafa1205bc11e Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 5 Apr 2025 17:57:05 +0200 Subject: [PATCH 579/586] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71fe5c5..6986c76 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Stacosys -Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide a basic alternative to comment hosting services like Disqus. Stacosys works with any static blog or even a simple HTML page. +Stacosys (aka STAtic blog COmment SYStem) is a fork of Pecosys trying to fix design drawbacks and provide a basic alternative to comment hosting services like Disqus. Stacosys works with any static blog or even a simple HTML page. ### Features overview From 633be8096b57afcf1f85c2e809ec34bfb4f71b65 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 Aug 2025 16:31:57 +0200 Subject: [PATCH 580/586] Update notified at published time if empty --- src/stacosys/db/dao.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stacosys/db/dao.py b/src/stacosys/db/dao.py index 307e439..5f21ae3 100644 --- a/src/stacosys/db/dao.py +++ b/src/stacosys/db/dao.py @@ -18,6 +18,9 @@ def notify_comment(comment: Comment): def publish_comment(comment: Comment): + # if published before notification is received + if comment.notified == None: + db()(db().comment.id == comment.id).update(notified=datetime.now()) db()(db().comment.id == comment.id).update(published=datetime.now()) db().commit() From 0ea85ff307637d5bfeb42c8e5f509301a21e93f5 Mon Sep 17 00:00:00 2001 From: Yax Date: Sun, 17 Aug 2025 16:36:26 +0200 Subject: [PATCH 581/586] Upd coverage --- coverage-badge.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage-badge.svg b/coverage-badge.svg index c3d15a1..dcb99ba 100644 --- a/coverage-badge.svg +++ b/coverage-badge.svg @@ -1 +1 @@ -coverage: 86.82%coverage86.82% \ No newline at end of file +coverage: 86.92%coverage86.92% \ No newline at end of file From ffa76d7822849ceb76b20652b74c053d1ea8512f Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 24 Sep 2025 13:22:33 +0200 Subject: [PATCH 582/586] Log if mail connection fails --- src/stacosys/run.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/stacosys/run.py b/src/stacosys/run.py index 42d903c..d311049 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -46,9 +46,13 @@ def configure_and_validate_mailer(config, logger): config.get(ConfigParameter.SMTP_LOGIN), config.get(ConfigParameter.SMTP_PASSWORD), ) - mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) - if not mailer.check(): - logger.error("Email configuration not working") + mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) + try: + if not mailer.check(): + logger.error("Email configuration not working") + sys.exit(1) + except Exception as e: + logging.error("Failed to check email: %s", e) sys.exit(1) return mailer From d47f444ba759b59d3166945fd743384a500b3abf Mon Sep 17 00:00:00 2001 From: Yax Date: Wed, 24 Sep 2025 13:22:57 +0200 Subject: [PATCH 583/586] fix RSS date format --- src/stacosys/service/rssfeed.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stacosys/service/rssfeed.py b/src/stacosys/service/rssfeed.py index 9c9ebe0..c9bea05 100644 --- a/src/stacosys/service/rssfeed.py +++ b/src/stacosys/service/rssfeed.py @@ -1,13 +1,15 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- -from datetime import datetime +from datetime import datetime, timezone import markdown import PyRSS2Gen from stacosys.db import dao +RFC_822_FORMAT = '%a, %d %b %Y %H:%M:%S +0000' + class Rss: def __init__(self) -> None: @@ -40,7 +42,7 @@ class Rss: link=item_link, description=markdownizer.convert(row.content), guid=PyRSS2Gen.Guid(f"{item_link}{row.id}"), - pubDate=row.published, + pubDate=self._to_rfc822(row.published) ) ) @@ -54,3 +56,7 @@ class Rss: ) with open(self._rss_file, "w", encoding="utf-8") as outfile: rss.write_xml(outfile, encoding="utf-8") + + def _to_rfc822(self, dt): + return dt.replace(tzinfo=timezone.utc) \ + .strftime('%a, %d %b %Y %H:%M:%S %z') From 4e1336d1ba68a82fb65195858270d4786c6ca9bc Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 29 Nov 2025 18:36:49 +0100 Subject: [PATCH 584/586] Black formatting --- src/stacosys/db/dao.py | 2 +- src/stacosys/run.py | 2 +- src/stacosys/service/rssfeed.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/stacosys/db/dao.py b/src/stacosys/db/dao.py index 5f21ae3..7088d87 100644 --- a/src/stacosys/db/dao.py +++ b/src/stacosys/db/dao.py @@ -20,7 +20,7 @@ def notify_comment(comment: Comment): def publish_comment(comment: Comment): # if published before notification is received if comment.notified == None: - db()(db().comment.id == comment.id).update(notified=datetime.now()) + db()(db().comment.id == comment.id).update(notified=datetime.now()) db()(db().comment.id == comment.id).update(published=datetime.now()) db().commit() diff --git a/src/stacosys/run.py b/src/stacosys/run.py index d311049..260e2a7 100644 --- a/src/stacosys/run.py +++ b/src/stacosys/run.py @@ -46,7 +46,7 @@ def configure_and_validate_mailer(config, logger): config.get(ConfigParameter.SMTP_LOGIN), config.get(ConfigParameter.SMTP_PASSWORD), ) - mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) + mailer.configure_destination(config.get(ConfigParameter.SITE_ADMIN_EMAIL)) try: if not mailer.check(): logger.error("Email configuration not working") diff --git a/src/stacosys/service/rssfeed.py b/src/stacosys/service/rssfeed.py index c9bea05..5c94d4e 100644 --- a/src/stacosys/service/rssfeed.py +++ b/src/stacosys/service/rssfeed.py @@ -8,7 +8,7 @@ import PyRSS2Gen from stacosys.db import dao -RFC_822_FORMAT = '%a, %d %b %Y %H:%M:%S +0000' +RFC_822_FORMAT = "%a, %d %b %Y %H:%M:%S +0000" class Rss: @@ -42,7 +42,7 @@ class Rss: link=item_link, description=markdownizer.convert(row.content), guid=PyRSS2Gen.Guid(f"{item_link}{row.id}"), - pubDate=self._to_rfc822(row.published) + pubDate=self._to_rfc822(row.published), ) ) @@ -58,5 +58,4 @@ class Rss: rss.write_xml(outfile, encoding="utf-8") def _to_rfc822(self, dt): - return dt.replace(tzinfo=timezone.utc) \ - .strftime('%a, %d %b %Y %H:%M:%S %z') + return dt.replace(tzinfo=timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z") From 5712fe9e001740ad0fa71c3c085c281cef148512 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 10 Jan 2026 17:38:01 +0100 Subject: [PATCH 585/586] Remove unused import --- src/stacosys/i18n/messages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stacosys/i18n/messages.py b/src/stacosys/i18n/messages.py index 374d066..0e38b1f 100644 --- a/src/stacosys/i18n/messages.py +++ b/src/stacosys/i18n/messages.py @@ -1,6 +1,5 @@ import configparser import importlib.resources -import os class Messages: From 784a84fb45c2151b194cf902d3cecd1f8281eea0 Mon Sep 17 00:00:00 2001 From: Yax Date: Sat, 10 Jan 2026 17:38:27 +0100 Subject: [PATCH 586/586] Run config-dev.ini by default --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9e40e0f..7b6b402 100644 --- a/Makefile +++ b/Makefile @@ -43,4 +43,4 @@ publish: # run run: - PYTHONPATH=src/ uv run python src/stacosys/run.py $(RUN_ARGS) + PYTHONPATH=src/ uv run python src/stacosys/run.py config-dev.ini
  • 9?uG;6JXDPtu17tlup#cGgk~?1Nc5Z5wH} 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­ml+D=R+taKLSXsz@g$ zc-y&+;wwh5i{McJL6zP*k`LyNw|&&uSmDunoh~+u4vIZjNCKR)f$9vV*OaK*$g5RPl?qg;Nj9Y%UK)C_gvbY^ywCMC@vwl!og15YF~wBxik9}h zKv5pq{%7FpQ4q6=52rL7TMn(b6km|}+S&_7CFUEpdIgdjZKFV}Ks~7=H#YAfGDy~( zvmeLvt&@Djc_uh22|9m5YZ^<>0wV{nJJbd>g9Z0nzuyD&nMTg=W-OA|hqZ2Jh^9uY zhycKP;0$JuLT+%Y^he&lP#g5Kf7o+4`W6~bA@i|x=e#p&EETjY2}?qb#cg|^hk2W>DV=Qdu&!`3 z4V*2K_nXOx%keywv+Yce>FazNm;IsUn=3Q880CWhoGdYm`fdQc0+{l9 z8_F0qe{Y2bB|7^Ttq)GW*>Uf1PQr~ay6JjEZDGZGwD@SUN6K(l?)VT_o8(mV$IB#g z@5-7qcdX^TsHjZ3d_6?e_j`WcYh_Z)V|?>zZdgrv6>;>ajati=FDcB_*2Y3JUi@^n z(^;68!6f0wsdsVr(4VcNSe20V`A6iiV~BGodJGV@dXgi1$(pUC(D2{;q%m@cnyknH zc-Jl0lp7&s6GNHrO;f<{pxfgLb{rv^CJLP%7B7i%4iKGk;A6)k3`dv@_yW$`8zVQ( z!)7BWe4kbfFy?*oeE#$Wzu^DEvCJ|0;kw;V&1@pSlQbZR0Nta9xiMSa{!xp}p=K!G3Eo zU3LSSABJb38TXA!QoZ zG7@x>(JRPxO;A26Zkc?pAj|Fv2zUJaUD>1aU$NQ4cg8;7^CPev1OIQ6O^zqIl*92V zu42K|wd={RH}p9VPf(amMc5JCxLStZ>!oCvrV7VQ~aLZ0X-1+8USnw}w9BF9krdEnfGw^>4?i1BKb-Ecr z2N^5fH?+H)LJBWR%WSudYO`%cjcrMpf0#R1}uwHGKn{3p>MwHRqE3fe< zM8OSo|DZto*Thds&|0(Ck#pFiA7!SUG*qzT3vP{y;d8;K(8G{cZ@;&Jd+wZbu>i0S z+p`OJ5H%@}e_3Uf7y5WS3*AR#bPNGc49OjC4MMOnUXET@4?$oDgWV{W?4dv)eWPso zAq40pNM>$mSsT!S_?{ih7U;nt<3h6mIRmY&07wFpnS;w{Cn9`LU)5Mh>?(Bve}n2` zTfuji2GfCJSEB({(sc)6D;rw6>*1g(Rpy+nK8_bGklr{O24x^*6}Lg7F-&F(a0e1td(X8cs1jcbK&+zbN@sO?JH>w;|&7r zO8=IvLAV*r;19v>J-jn84Mt@1i4P!W4fdV|x$_PvpG)pH>|;Hj4|uL%w7MXDtGRd~ z^k(=+ePnEmP_{Q-X2e04dPbeY~ z0QVm?J&``+-?+D3I)SR|0a}y520&Qfe{_o?_2%bY@q$O|Xxu(W4VVI5pZT|7?nri) zVA`)9b0*YnFA&i%|Dbj)_CO=x+{8Vh4Y*h(^fv#8ID`?qz=}&q<$R|CUn{@}_aW%)#+H55;+yTo5iUPEIa99v*Ip0H1&WKOaB8ppck| zppd8#Kfj2Sh^V-Pq@<*PF!YFSzGNr~T&fWU`$IJtPaxOgQ5`2{8Z&tks=L?M6; zU;_r71UN)NU{Mge3y?j8wH~joV!@_SzL`Fs5yZ<0QA@N~S`jd>ztn8=H z@P$RiFH1_x%3syi)e{;To0{JeJG;7jdO!5_kBp9uPfSitlPQZ!%U@Pj*S@aPzyJ8T z&Ddf7+WpN10>J;qI^6#a_CL5p54kuvIl-Jfzqvph5r+#b%E=|K!7XNJ%i|Fuep2%; zM8YWTMa_F&1uZ+8#_d{T<%C`$C-X#XPn-+{&bzsUXz?7z9lfB+bDI6SZ@fB+64 zLxiJ@vbwF^$^3l}=5!(E#E5+Ufmi#u+73Tlb1vbT&b6THNXVnK<{`^FAVu(3Qb;LO zrA0APG`Upi$m@n^1MKT;<|8`Hi0Mpe)~<|R?hHC|SwKVWSCx`nW{NiJ)St?q0CPDG z(=J!7gCdq3le}m|p3Rqe=m|d*^X5c{5*25j%^FtnHfWJ zY}>My2Ig=xm>#{*ySd@rn2#9Nef4AJjiWW2cK!Z+rMAVXf%I4^ZV49?1vPA_p%EVL zsu?jxd_P(S>TB=xQ<6A%1~x;6 z+;x`of#qDst89R8rpIW`mkr!UqHY{DhIK-+Z|cW!h*@E9m?bfMp3`jk8o|~zi7NB+ z?75T8_Ug<$cn8&rj_4dFjIm&>ktF%7ls(J1%L=o`$8`1uqYH>zGc#tBO7Sz@w;SFl z&<`pL`+Xinz1O^Gp2q!8IVR#A=j32E(MoO2Xqj7j8bIk6Md`U?GyCfoUetmx6N{xGvQr5ypGaF1rp&WW1xu_HR81}7l0ZD3=W(+oa zF`Y|@!hInX?^5qgduAE#KCjXz=AQ8=B~^PZd=9=>e%dir*+n>IV+!G0a9=l~i)UQB zKxDcXTR0fjHwbox4Rf+x6u5xB5Oc ziHBbIu-i*2m;3gdD*)@R= zs&RjPA%g*Z7#K2MRRg}>fQ!xd!5t&A(h_@+e}Yd0tRTf> zK1>{QXiv{#$>W_-t&O!FWmDZcIIj>f(fSgz+sIDsAFZ})9Q-vNR31G) zSKx}Ox0s`TN?v?`wy`U&SbB|h=+Js~kZ&LwxkbtDxw7;MU2#lh)GKL`G4IZp3|x^< zxIt+Yo|k=QXyd&1uAqE>DCC+Ou_Ga?fNx(q;5mB!np=hCNMGo!*_N6UV00+E7}hFXzEzG_2HoXEomkr?FO4RlzZ z&#SAMPh1+=K)g=sgY)@1^ESjG|W9yG4zAnxG|;R zXryP)v4uo^;l&LrfsHFc!Qa9!$hjG-zIlQ3vQxrkWRQ!&q+utk#e$T&s$P0_96VlQ z#W@nS8auh#p@4W0-E4w=FEX%3tWfE(P|-dUkx8vwP;T98FO|fod9C+#x40Ftfw)NS zI{LjuQZ^0T<*LXU{8WV^j@qPLh}bi_^wu}t+ymX-pY;;#4Vvi)E(e!3F&7xoBl;IU z^JAl9B`_%ZT`_dOYM3;0wJAcg$L>d`f zQ=LnhSEtOjNGIYj=RBZ)oVGsO(NYZ`ZGEz_IUS|Y2))ydD&^sApuYP1iQ%W}cIo}d z@m3jqt19Lrlbp`mZ|h;z*Oa>6SOx{1#8#D+`q^6xMM-{bW>m{DbtpLNp{{x534P?E z%hm3A)nvoskFWe?EKCB2E$N9xNs6LBZq8(E>VQ`iBscVierX@r-mOML<4Y5Z)NWXv zDhS&){qpp~p6>U1r~sqm!}xo#ZE9>F`e#MdY;VrKC2cFGV$RLg+12%iQwbjL{dIK0 z1V7fR&>AF#$V+RTbR-MUS;oCgd`Vky91UBpqpf6e?m$H@*1BnSo;&L-*y0e^&%L1W zm;!D8G4*6Ur+MRA@Iz150DU20`!o$|YaW(!NAC?PU&>(M%V5#G|2{I6X_2K4e)Lhr z;@Y}C3A*E)L}0Xyg%iG!-4KKip;1>~BYDp?I}>&{Mr(#A3~H7H`8(^1b*l)aUu&DV zJX_wHJ&n65bR?xTwK5=gu{mdI8_U0E{&!dl)@Q(O{3jK)v_ph7;p!W{d0H(fCJqF@ zQ_Vr=Y;iCB@=SPh^tN|0<7mF}lo4s9(KTu7Qp~eQuTeMy|FbJtTqWYKHxu$d{tgb@ zy1vmq@9;Rr9lyGr%IhyqUchE|liTj~NoOF|zNKmK@T?@}g15+J%>GqNx<7MbeU)-y zmYh{7a6THdBlR|*tro(lf6@AbF#a+#UPsTdz`_+u>qx+$mpY_ok)LO*w5;m>x#A2r z@J=yXbVzQAN_%mmtGgiNeemN>lA+|D5Sqdg7ky|mp!Jv4=A^3Ex4gZ+7}Jl7bEe1Q(ML*e}UtzZ1ool2dgxs=dnPr{3n`t6FT0sw6MI1K&}J z*qhuA*9|F!5YP0%{7QP4)5}(smB$idn~DZ^B%x2+$D$rlou?yowpOlm%sML3#V*3b zG=;a~>uxCYy1EIbGC{G#x2b2N`F`p3E^9DwU+W;h7&^kVtg`c>Thb7FKP&VvUB`r#^oDfv^s6u zeI*VyQ13=oChmNyW;}2Iv>BTO%W=#^@SYTC`q{@bg~Cnt7J!|Jr0^mGL>!qEb&bV$ zf072bmi@Rk#;_xjShm4*rx@kUu)4vjHuXTHIFHQ!FdI1M)-mcy_f1u zgQ&(^?-cQV(peg{=??CnWq+*)RFBlRTo5Aa!3`#0dVl?BIBkC>&f0u8-CVg`5l^pv zfRM=Pij~+mzOHI{EU2txmlTjzds9A%d@}iYTD3W^7Se>PyM4kdo#um@1rpo;sCQP|7cBx=~bTllzr%XY*SflH=}6K&3~^&Z8bGk Hi#`1x$O7_n literal 0 HcmV?d00001 diff --git a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg b/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a23176c70ab449fcdd43b5da799197a945563ae GIT binary patch literal 2637 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/framasoft.png b/demo/public/img/framasoft.png new file mode 100644 index 0000000000000000000000000000000000000000..71e74c308106e6833c9a841ec4edbf07f7ecccfb GIT binary patch literal 19259 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/id-150.jpg b/demo/public/img/id-150.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebc872a5c1180aceda7936ccfb7361ac9a4f68a4 GIT binary patch literal 19264 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 literal 0 HcmV?d00001 diff --git a/demo/public/img/license.png b/demo/public/img/license.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2a0f10722d3fabffe8af5d5eed09866e25909a GIT binary patch literal 5460 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 Ng2}4L)JmC!{ST_+RpI~u literal 0 HcmV?d00001 diff --git a/demo/public/index.html b/demo/public/index.html new file mode 100644 index 0000000..b8ddee6 --- /dev/null +++ b/demo/public/index.html @@ -0,0 +1,208 @@ + + + + + + + + Bilan 2014 de l'hébergement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +